Upgraded truckroll view
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.bower.json b/views/ngXosViews/serviceGrid/src/vendor/lodash/.bower.json
new file mode 100644
index 0000000..6b70939
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.bower.json
@@ -0,0 +1,14 @@
+{
+  "name": "lodash",
+  "homepage": "https://github.com/lodash/lodash",
+  "version": "4.11.2",
+  "_release": "4.11.2",
+  "_resolution": {
+    "type": "version",
+    "tag": "4.11.2",
+    "commit": "64fbb18fc7a4dd44240e25ed9cac2576b16f45a3"
+  },
+  "_source": "https://github.com/lodash/lodash.git",
+  "_target": "~4.11.1",
+  "_originalSource": "lodash"
+}
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.editorconfig b/views/ngXosViews/serviceGrid/src/vendor/lodash/.editorconfig
new file mode 100644
index 0000000..b889a36
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.editorconfig
@@ -0,0 +1,12 @@
+# This file is for unifying the coding style for different editors and IDEs
+# editorconfig.org
+
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+indent_size = 2
+indent_style = space
+insert_final_newline = true
+trim_trailing_whitespace = true
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.gitattributes b/views/ngXosViews/serviceGrid/src/vendor/lodash/.gitattributes
new file mode 100644
index 0000000..176a458
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.gitattributes
@@ -0,0 +1 @@
+* text=auto
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.github/CONTRIBUTING.md b/views/ngXosViews/serviceGrid/src/vendor/lodash/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..b3427fd
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.github/CONTRIBUTING.md
@@ -0,0 +1,78 @@
+# Contributing to Lodash
+
+Contributions are always welcome. Before contributing please read the
+[code of conduct](https://github.com/lodash/lodash/blob/master/CODE_OF_CONDUCT.md)
+& [search the issue tracker](https://github.com/lodash/lodash/issues); your issue
+may have already been discussed or fixed in `master`. To contribute,
+[fork](https://help.github.com/articles/fork-a-repo/) Lodash, commit your changes,
+& [send a pull request](https://help.github.com/articles/using-pull-requests/).
+
+## Feature Requests
+
+Feature requests should be submitted in the
+[issue tracker](https://github.com/lodash/lodash/issues), with a description of
+the expected behavior & use case, where they’ll remain closed until sufficient interest,
+[e.g. :+1: reactions](https://help.github.com/articles/about-discussions-in-issues-and-pull-requests/),
+has been shown by the community. Before submitting a request, please search for
+similar ones in the
+[closed issues](https://github.com/lodash/lodash/issues?q=is%3Aissue+is%3Aclosed+label%3Aenhancement).
+
+## Pull Requests
+
+For additions or bug fixes you should only need to modify `lodash.js`. Include
+updated unit tests in the `test` directory as part of your pull request. Don’t
+worry about regenerating the `dist/` or `doc/` files.
+
+Before running the unit tests you’ll need to install, `npm i`,
+[development dependencies](https://docs.npmjs.com/files/package.json#devdependencies).
+Run unit tests from the command-line via `npm test`, or open `test/index.html` &
+`test/fp.html` in a web browser. The [Backbone](http://backbonejs.org/) &
+[Underscore](http://underscorejs.org/) test suites are included as well.
+
+## Contributor License Agreement
+
+Lodash is a member of the [jQuery Foundation](https://jquery.org/).
+As such, we request that all contributors sign the jQuery Foundation
+[contributor license agreement (CLA)](https://contribute.jquery.org/CLA/).
+
+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/).
+
+## Coding Guidelines
+
+In addition to the following guidelines, please follow the conventions already
+established in the code.
+
+- **Spacing**:<br>
+  Use two spaces for indentation. No tabs.
+
+- **Naming**:<br>
+  Keep variable & method names concise & descriptive.<br>
+  Variable names `index`, `collection`, & `callback` are preferable to
+  `i`, `arr`, & `fn`.
+
+- **Quotes**:<br>
+  Single-quoted strings are preferred to double-quoted strings; however,
+  please use a double-quoted string if the value contains a single-quote
+  character to avoid unnecessary escaping.
+
+- **Comments**:<br>
+  Please use single-line comments to annotate significant additions, &
+  [JSDoc-style](http://www.2ality.com/2011/08/jsdoc-intro.html) comments for
+  functions.
+
+Guidelines are enforced using [JSCS](https://www.npmjs.com/package/jscs):
+```bash
+$ npm run style
+```
+
+## Tips
+
+You can opt-in to a pre-push git hook by adding an `.opt-in` file to the root of
+the project containing:
+```txt
+pre-push
+```
+
+With that, when you `git push`, the pre-push git hook will trigger and execute
+`npm run validate`.
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.gitignore b/views/ngXosViews/serviceGrid/src/vendor/lodash/.gitignore
new file mode 100644
index 0000000..6eb2db8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.gitignore
@@ -0,0 +1,9 @@
+.DS_Store
+*.custom.*
+*.log
+*.map
+lodash.compat.min.js
+coverage
+node_modules
+.opt-in
+.opt-out
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.jscsrc b/views/ngXosViews/serviceGrid/src/vendor/lodash/.jscsrc
new file mode 100644
index 0000000..5f44ab8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.jscsrc
@@ -0,0 +1,97 @@
+{
+    "maxErrors": "2000",
+    "maximumLineLength": {
+      "value": 180,
+      "allExcept": ["comments", "functionSignature", "regex"]
+    },
+    "requireCurlyBraces": [
+        "if",
+        "else",
+        "for",
+        "while",
+        "do",
+        "try",
+        "catch"
+    ],
+    "requireOperatorBeforeLineBreak": [
+        "=",
+        "+",
+        "-",
+        "/",
+        "*",
+        "==",
+        "===",
+        "!=",
+        "!==",
+        ">",
+        ">=",
+        "<",
+        "<="
+    ],
+    "requireSpaceAfterKeywords": [
+      "if",
+      "else",
+      "for",
+      "while",
+      "do",
+      "switch",
+      "return",
+      "try",
+      "catch"
+    ],
+    "requireSpaceBeforeBinaryOperators": [
+        "=", "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=",
+        "&=", "|=", "^=", "+=",
+
+        "+", "-", "*", "/", "%", "<<", ">>", ">>>", "&",
+        "|", "^", "&&", "||", "===", "==", ">=",
+        "<=", "<", ">", "!=", "!=="
+    ],
+    "requireSpacesInFunctionExpression": {
+        "beforeOpeningCurlyBrace": true
+    },
+    "requireCamelCaseOrUpperCaseIdentifiers": true,
+    "requireDotNotation": { "allExcept": ["keywords"] },
+    "requireEarlyReturn": true,
+    "requireLineFeedAtFileEnd": true,
+    "requireSemicolons": true,
+    "requireSpaceAfterBinaryOperators": true,
+    "requireSpacesInConditionalExpression": true,
+    "requireSpaceBeforeObjectValues": true,
+    "requireSpaceBeforeBlockStatements": true,
+    "requireSpacesInForStatement": true,
+
+    "validateIndentation": 2,
+    "validateParameterSeparator": ", ",
+    "validateQuoteMarks": { "mark": "'", "escape": true },
+
+    "disallowSpacesInAnonymousFunctionExpression": {
+        "beforeOpeningRoundBrace": true
+    },
+    "disallowSpacesInFunctionDeclaration": {
+        "beforeOpeningRoundBrace": true
+    },
+    "disallowSpacesInFunctionExpression": {
+        "beforeOpeningRoundBrace": true
+    },
+    "disallowKeywords": ["with"],
+    "disallowMixedSpacesAndTabs": true,
+    "disallowMultipleLineBreaks": true,
+    "disallowNewlineBeforeBlockStatements": true,
+    "disallowSpaceAfterObjectKeys": true,
+    "disallowSpaceAfterPrefixUnaryOperators": true,
+    "disallowSpacesInCallExpression": true,
+    "disallowSpacesInsideArrayBrackets": true,
+    "disallowSpacesInsideParentheses": true,
+    "disallowTrailingWhitespace": true,
+    "disallowUnusedVariables": true,
+
+    "jsDoc": {
+        "checkRedundantAccess": true,
+        "checkTypes": true,
+        "requireNewlineAfterDescription": true,
+        "requireParamDescription": true,
+        "requireParamTypes": true,
+        "requireReturnTypes": true
+    }
+}
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/.travis.yml b/views/ngXosViews/serviceGrid/src/vendor/lodash/.travis.yml
new file mode 100644
index 0000000..161f0cd
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/.travis.yml
@@ -0,0 +1,77 @@
+language: node_js
+sudo: false
+node_js:
+  - "5"
+env:
+  global:
+    - BIN="node" ISTANBUL=false OPTION=""
+    - SAUCE_LABS=false SAUCE_USERNAME="lodash"
+    - secure: "tg1JFsIFnxzLaTboFPOnm+aJCuMm5+JdhLlESlqg9x3fwro++7KCnwHKLNovhchaPe4otC43ZMB/nfWhDnDm11dKbm/V6HlTkED+dadTsaLxVDg6J+7yK41QhokBPJOxLV78iDaNaAQVYEirAgZ0yn8kFubxmNKV+bpCGQNc9yU="
+  matrix:
+    -
+    - BIN="phantomjs"
+    - ISTANBUL=true
+    - SAUCE_LABS=true
+matrix:
+  include:
+    - node_js: "0.10"
+      env:
+    - node_js: "0.12"
+      env:
+    - node_js: "4"
+      env:
+git:
+  depth: 10
+branches:
+  only:
+    - master
+notifications:
+  webhooks:
+    urls:
+      - https://webhooks.gitter.im/e/4aab6358b0e9aed0b628
+    on_success: change
+    on_failure: always
+before_install:
+  - "nvm use $TRAVIS_NODE_VERSION"
+  - "npm set loglevel error"
+  - "npm set progress false"
+  - "npm i -g npm@\"^2.0.0\""
+  - |
+      PATTERN[0]="|\s*if\s*\(isHostObject\b[\s\S]+?\}(?=\n)|"
+      PATTERN[1]="|\s*if\s*\(enumerate\b[\s\S]+?\};\s*\}|"
+      PATTERN[2]="|\s*while\s*\([^)]+\)\s*\{\s*iteratee\(index\);\s*\}|"
+      PATTERN[3]="|\s*else\s*\{\s*assocSet\(data\b[\s\S]+?\}|"
+      PATTERN[4]="|\bcase\s+(?:dataView|set|map|weakMap)CtorString:.+|g"
+      PATTERN[5]="|\bindex,\s*iterable\)\s*===\s*false\)[^}]+?(break;)|"
+      PATTERN[6]="|\s*if\s*\(\!lodashFunc\)\s*\{\s*return;\s*\}|"
+      PATTERN[7]="|\s*define\([\s\S]+?\);|"
+      PATTERN[8]="|\s*root\._\s*=\s*_;|"
+
+      if [ $ISTANBUL == true ]; then
+        set -e
+        for PTRN in ${PATTERN[@]}; do
+          node ./test/remove.js "$PTRN" ./lodash.js
+        done
+      fi
+  - "git clone --depth=10 --branch=master git://github.com/lodash/lodash-cli ./node_modules/lodash-cli && mkdir $_/node_modules && cd $_ && ln -s ../../../ ./lodash && cd ../ && npm i && cd ../../"
+  - "node ./node_modules/lodash-cli/bin/lodash -o ./dist/lodash.js"
+script:
+  - "[ $ISTANBUL == false ]   || istanbul cover -x \"**/vendor/**\" --report lcovonly ./test/test.js -- ./lodash.js"
+  - "[ $ISTANBUL == false ]   || [ $TRAVIS_SECURE_ENV_VARS == false ] || (cat ./coverage/lcov.info | coveralls) || true"
+  - "[ $ISTANBUL == false ]   || [ $TRAVIS_SECURE_ENV_VARS == false ] || (cat ./coverage/coverage.json | codecov) || true"
+  - "[ $SAUCE_LABS == true ]  || [ $ISTANBUL == true ] || cd ./test"
+  - "[ $SAUCE_LABS == true ]  || [ $ISTANBUL == true ] || $BIN $OPTION ./test.js ../lodash.js"
+  - "[ $SAUCE_LABS == true ]  || [ $ISTANBUL == true ] || [ $TRAVIS_SECURE_ENV_VARS == false ] || $BIN $OPTION ./test.js ../dist/lodash.min.js"
+  - "[ $SAUCE_LABS == false ] || rm -rf ./node_modules/lodash"
+  - "[ $SAUCE_LABS == false ] || ($BIN ./node_modules/lodash-cli/bin/lodash -d -o ./node_modules/lodash/index.js && cd ./node_modules/lodash/ && ln -s ./index.js ./lodash.js && cd ../../)"
+  - "[ $SAUCE_LABS == false ] || $BIN ./node_modules/lodash-cli/bin/lodash core -o ./dist/lodash.core.js"
+  - "[ $SAUCE_LABS == false ] || npm run build"
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"lodash tests\"     runner=\"test/index.html?build=../dist/lodash.js&noglobals=true\"     tags=\"development\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"lodash tests\"     runner=\"test/index.html?build=../dist/lodash.min.js&noglobals=true\" tags=\"production\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"lodash-fp tests\"  runner=\"test/fp.html?noglobals=true\"                                tags=\"development\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"underscore tests\" runner=\"test/underscore.html?build=../dist/lodash.js\"               tags=\"development,underscore\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"underscore tests\" runner=\"test/underscore.html?build=../dist/lodash.min.js\"           tags=\"production,underscore\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"backbone tests\"   runner=\"test/backbone.html?build=../dist/lodash.js\"                 tags=\"development,backbone\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"backbone tests\"   runner=\"test/backbone.html?build=../dist/lodash.min.js\"             tags=\"production,backbone\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"backbone tests\"   runner=\"test/backbone.html?build=../dist/lodash.core.js\"            tags=\"development,backbone\""
+  - "[ $SAUCE_LABS == false ] || $BIN ./test/saucelabs.js name=\"backbone tests\"   runner=\"test/backbone.html?build=../dist/lodash.core.min.js\"        tags=\"production,backbone\""
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/LICENSE b/views/ngXosViews/serviceGrid/src/vendor/lodash/LICENSE
new file mode 100644
index 0000000..e0c69d5
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/LICENSE
@@ -0,0 +1,47 @@
+Copyright jQuery Foundation and other contributors <https://jquery.org/>
+
+Based on Underscore.js, copyright Jeremy Ashkenas,
+DocumentCloud and Investigative Reporters & Editors <http://underscorejs.org/>
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/lodash/lodash
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+Copyright and related rights for sample code are waived via CC0. Sample
+code is defined as all source code displayed within the prose of the
+documentation.
+
+CC0: http://creativecommons.org/publicdomain/zero/1.0/
+
+====
+
+Files located in the node_modules and vendor directories are externally
+maintained libraries used by this software which have their own
+licenses; we recommend you read them, as their terms may differ from the
+terms above.
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/README.md b/views/ngXosViews/serviceGrid/src/vendor/lodash/README.md
new file mode 100644
index 0000000..fe14559
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/README.md
@@ -0,0 +1,46 @@
+# lodash v4.11.2
+
+[Site](https://lodash.com/) |
+[Docs](https://lodash.com/docs) |
+[FP Guide](https://github.com/lodash/lodash/wiki/FP-Guide) |
+[Contributing](https://github.com/lodash/lodash/blob/4.11.2/.github/CONTRIBUTING.md) |
+[Wiki](https://github.com/lodash/lodash/wiki "Changelog, Roadmap, etc.") |
+[Code of Conduct](https://jquery.org/conduct/) |
+[Twitter](https://twitter.com/bestiejs) |
+[Chat](https://gitter.im/lodash/lodash)
+
+The [Lodash](https://lodash.com/) library exported as a [UMD](https://github.com/umdjs/umd) module.
+
+Generated using [lodash-cli](https://www.npmjs.com/package/lodash-cli):
+```bash
+$ npm run build
+$ lodash -o ./dist/lodash.js
+$ lodash core -o ./dist/lodash.core.js
+```
+
+## Download
+
+Lodash is released under the [MIT license](https://raw.githubusercontent.com/lodash/lodash/4.11.2/LICENSE) & supports [modern environments](#support).<br>
+Review the [build differences](https://github.com/lodash/lodash/wiki/build-differences) & pick one that’s right for you.
+
+ * [Core build](https://raw.githubusercontent.com/lodash/lodash/4.11.2/dist/lodash.core.js) ([~4 kB gzipped](https://raw.githubusercontent.com/lodash/lodash/4.11.2/dist/lodash.core.min.js))
+ * [Full build](https://raw.githubusercontent.com/lodash/lodash/4.11.2/dist/lodash.js) ([~22 kB gzipped](https://raw.githubusercontent.com/lodash/lodash/4.11.2/dist/lodash.min.js))
+ * [CDN copies](https://www.jsdelivr.com/projects/lodash)
+
+## Why Lodash?
+
+Lodash makes JavaScript easier by taking the hassle out of working with arrays,<br>
+numbers, objects, strings, etc. Lodash’s modular methods are great for:
+
+* Iterating arrays, objects, & strings
+* Manipulating & testing values
+* Creating composite functions
+
+## Module Formats
+
+Lodash is available in a [variety of builds](https://lodash.com/custom-builds) & module formats.
+
+ * [lodash](https://www.npmjs.com/package/lodash) & [per method packages](https://www.npmjs.com/browse/keyword/lodash-modularized)
+ * [lodash-amd](https://www.npmjs.com/package/lodash-amd)
+ * [lodash-es](https://www.npmjs.com/package/lodash-es) & [babel-plugin-lodash](https://www.npmjs.com/package/babel-plugin-lodash)
+ * [lodash/fp](https://github.com/lodash/lodash/tree/4.11.2-npm/fp)
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.core.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.core.js
new file mode 100644
index 0000000..73b541b
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.core.js
@@ -0,0 +1,3978 @@
+/**
+ * @license
+ * lodash 4.11.2 (Custom Build) <https://lodash.com/>
+ * Build: `lodash core -o ./dist/lodash.core.js`
+ * Copyright jQuery Foundation and other contributors <https://jquery.org/>
+ * Released under MIT license <https://lodash.com/license>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the semantic version number. */
+  var VERSION = '4.11.2';
+
+  /** Used as the `TypeError` message for "Functions" methods. */
+  var FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used to compose bitmasks for wrapper metadata. */
+  var BIND_FLAG = 1,
+      PARTIAL_FLAG = 32;
+
+  /** Used to compose bitmasks for comparison styles. */
+  var UNORDERED_COMPARE_FLAG = 1,
+      PARTIAL_COMPARE_FLAG = 2;
+
+  /** Used as references for various `Number` constants. */
+  var INFINITY = 1 / 0,
+      MAX_SAFE_INTEGER = 9007199254740991;
+
+  /** `Object#toString` result references. */
+  var argsTag = '[object Arguments]',
+      arrayTag = '[object Array]',
+      boolTag = '[object Boolean]',
+      dateTag = '[object Date]',
+      errorTag = '[object Error]',
+      funcTag = '[object Function]',
+      genTag = '[object GeneratorFunction]',
+      numberTag = '[object Number]',
+      objectTag = '[object Object]',
+      regexpTag = '[object RegExp]',
+      stringTag = '[object String]';
+
+  /** Used to match HTML entities and HTML characters. */
+  var reUnescapedHtml = /[&<>"'`]/g,
+      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+  /** Used to detect unsigned integer values. */
+  var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+  /** Used to map characters to HTML entities. */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;',
+    '`': '&#96;'
+  };
+
+  /** Used to determine if values are of the language type `Object`. */
+  var objectTypes = {
+    'function': true,
+    'object': true
+  };
+
+  /** Detect free variable `exports`. */
+  var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType)
+    ? exports
+    : undefined;
+
+  /** Detect free variable `module`. */
+  var freeModule = (objectTypes[typeof module] && module && !module.nodeType)
+    ? module
+    : undefined;
+
+  /** Detect the popular CommonJS extension `module.exports`. */
+  var moduleExports = (freeModule && freeModule.exports === freeExports)
+    ? freeExports
+    : undefined;
+
+  /** Detect free variable `global` from Node.js. */
+  var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global);
+
+  /** Detect free variable `self`. */
+  var freeSelf = checkGlobal(objectTypes[typeof self] && self);
+
+  /** Detect free variable `window`. */
+  var freeWindow = checkGlobal(objectTypes[typeof window] && window);
+
+  /** Detect `this` as the global object. */
+  var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
+
+  /**
+   * Used as a reference to the global object.
+   *
+   * The `this` value is used if it's the global object to avoid Greasemonkey's
+   * restricted `window` object, otherwise the `window` object is used.
+   */
+  var root = freeGlobal ||
+    ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) ||
+      freeSelf || thisGlobal || Function('return this')();
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a new array concatenating `array` with `other`.
+   *
+   * @private
+   * @param {Array} array The first array to concatenate.
+   * @param {Array} other The second array to concatenate.
+   * @returns {Array} Returns the new concatenated array.
+   */
+  function arrayConcat(array, other) {
+    return arrayPush(copyArray(array), values);
+  }
+
+  /**
+   * Appends the elements of `values` to `array`.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {Array} values The values to append.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayPush(array, values) {
+    array.push.apply(array, values);
+    return array;
+  }
+
+  /**
+   * The base implementation of methods like `_.find` and `_.findKey`, without
+   * support for iteratee shorthands, which iterates over `collection` using
+   * `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to search.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @param {boolean} [retKey] Specify returning the key of the found element
+   *  instead of the element itself.
+   * @returns {*} Returns the found element or its key, else `undefined`.
+   */
+  function baseFind(collection, predicate, eachFunc, retKey) {
+    var result;
+    eachFunc(collection, function(value, key, collection) {
+      if (predicate(value, key, collection)) {
+        result = retKey ? key : value;
+        return false;
+      }
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.reduce` and `_.reduceRight`, without support
+   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} accumulator The initial value.
+   * @param {boolean} initAccum Specify using the first or last element of
+   *  `collection` as the initial value.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the accumulated value.
+   */
+  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+    eachFunc(collection, function(value, index, collection) {
+      accumulator = initAccum
+        ? (initAccum = false, value)
+        : iteratee(accumulator, value, index, collection);
+    });
+    return accumulator;
+  }
+
+  /**
+   * The base implementation of `_.times` without support for iteratee shorthands
+   * or max array length checks.
+   *
+   * @private
+   * @param {number} n The number of times to invoke `iteratee`.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the array of results.
+   */
+  function baseTimes(n, iteratee) {
+    var index = -1,
+        result = Array(n);
+
+    while (++index < n) {
+      result[index] = iteratee(index);
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.values` and `_.valuesIn` which creates an
+   * array of `object` property values corresponding to the property names
+   * of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the array of property values.
+   */
+  function baseValues(object, props) {
+    return baseMap(props, function(key) {
+      return object[key];
+    });
+  }
+
+  /**
+   * Checks if `value` is a global object.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {null|Object} Returns `value` if it's a global object, else `null`.
+   */
+  function checkGlobal(value) {
+    return (value && value.Object === Object) ? value : null;
+  }
+
+  /**
+   * Used by `_.escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeHtmlChar(chr) {
+    return htmlEscapes[chr];
+  }
+
+  /**
+   * Checks if `value` is a host object in IE < 9.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+   */
+  function isHostObject(value) {
+    // Many host objects are `Object` objects that can coerce to strings
+    // despite having improperly defined `toString` methods.
+    var result = false;
+    if (value != null && typeof value.toString != 'function') {
+      try {
+        result = !!(value + '');
+      } catch (e) {}
+    }
+    return result;
+  }
+
+  /**
+   * Converts `iterator` to an array.
+   *
+   * @private
+   * @param {Object} iterator The iterator to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function iteratorToArray(iterator) {
+    var data,
+        result = [];
+
+    while (!(data = iterator.next()).done) {
+      result.push(data.value);
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /** Used for built-in method references. */
+  var arrayProto = Array.prototype,
+      objectProto = Object.prototype;
+
+  /** Used to check objects for own properties. */
+  var hasOwnProperty = objectProto.hasOwnProperty;
+
+  /** Used to generate unique IDs. */
+  var idCounter = 0;
+
+  /**
+   * Used to resolve the
+   * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+   * of values.
+   */
+  var objectToString = objectProto.toString;
+
+  /** Used to restore the original `_` reference in `_.noConflict`. */
+  var oldDash = root._;
+
+  /** Built-in value references. */
+  var Reflect = root.Reflect,
+      Symbol = root.Symbol,
+      Uint8Array = root.Uint8Array,
+      enumerate = Reflect ? Reflect.enumerate : undefined,
+      objectCreate = Object.create,
+      propertyIsEnumerable = objectProto.propertyIsEnumerable;
+
+  /* Built-in method references for those with the same name as other `lodash` methods. */
+  var nativeIsFinite = root.isFinite,
+      nativeKeys = Object.keys,
+      nativeMax = Math.max;
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Creates a `lodash` object which wraps `value` to enable implicit method
+   * chain sequences. Methods that operate on and return arrays, collections,
+   * and functions can be chained together. Methods that retrieve a single value
+   * or may return a primitive value will automatically end the chain sequence
+   * and return the unwrapped value. Otherwise, the value must be unwrapped
+   * with `_#value`.
+   *
+   * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+   * enabled using `_.chain`.
+   *
+   * The execution of chained methods is lazy, that is, it's deferred until
+   * `_#value` is implicitly or explicitly called.
+   *
+   * Lazy evaluation allows several methods to support shortcut fusion.
+   * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+   * the creation of intermediate arrays and can greatly reduce the number of
+   * iteratee executions. Sections of a chain sequence qualify for shortcut
+   * fusion if the section is applied to an array of at least `200` elements
+   * and any iteratees accept only one argument. The heuristic for whether a
+   * section qualifies for shortcut fusion is subject to change.
+   *
+   * Chaining is supported in custom builds as long as the `_#value` method is
+   * directly or indirectly included in the build.
+   *
+   * In addition to lodash methods, wrappers have `Array` and `String` methods.
+   *
+   * The wrapper `Array` methods are:
+   * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+   *
+   * The wrapper `String` methods are:
+   * `replace` and `split`
+   *
+   * The wrapper methods that support shortcut fusion are:
+   * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+   * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+   * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+   *
+   * The chainable wrapper methods are:
+   * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+   * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+   * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+   * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+   * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+   * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+   * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+   * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+   * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+   * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+   * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+   * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+   * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+   * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+   * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+   * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+   * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+   * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+   * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+   * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+   * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+   * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+   * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+   * `zipObject`, `zipObjectDeep`, and `zipWith`
+   *
+   * The wrapper methods that are **not** chainable by default are:
+   * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+   * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `deburr`, `divide`, `each`,
+   * `eachRight`, `endsWith`, `eq`, `escape`, `escapeRegExp`, `every`, `find`,
+   * `findIndex`, `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `first`,
+   * `floor`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`,
+   * `forOwnRight`, `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`,
+   * `includes`, `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`,
+   * `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, `isBuffer`,
+   * `isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, `isError`,
+   * `isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, `isMatch`,
+   * `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`,
+   * `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`,
+   * `isSet`, `isString`, `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`,
+   * `join`, `kebabCase`, `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`,
+   * `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, `min`, `minBy`, `multiply`,
+   * `noConflict`, `noop`, `now`, `nth`, `pad`, `padEnd`, `padStart`, `parseInt`,
+   * `pop`, `random`, `reduce`, `reduceRight`, `repeat`, `result`, `round`,
+   * `runInContext`, `sample`, `shift`, `size`, `snakeCase`, `some`, `sortedIndex`,
+   * `sortedIndexBy`, `sortedLastIndex`, `sortedLastIndexBy`, `startCase`,
+   * `startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toInteger`,
+   * `toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, `toString`,
+   * `toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`,
+   * `uniqueId`, `upperCase`, `upperFirst`, `value`, and `words`
+   *
+   * @name _
+   * @constructor
+   * @category Seq
+   * @param {*} value The value to wrap in a `lodash` instance.
+   * @returns {Object} Returns the new `lodash` wrapper instance.
+   * @example
+   *
+   * function square(n) {
+   *   return n * n;
+   * }
+   *
+   * var wrapped = _([1, 2, 3]);
+   *
+   * // Returns an unwrapped value.
+   * wrapped.reduce(_.add);
+   * // => 6
+   *
+   * // Returns a wrapped value.
+   * var squares = wrapped.map(square);
+   *
+   * _.isArray(squares);
+   * // => false
+   *
+   * _.isArray(squares.value());
+   * // => true
+   */
+  function lodash(value) {
+    return value instanceof LodashWrapper
+      ? value
+      : new LodashWrapper(value);
+  }
+
+  /**
+   * The base constructor for creating `lodash` wrapper objects.
+   *
+   * @private
+   * @param {*} value The value to wrap.
+   * @param {boolean} [chainAll] Enable explicit method chain sequences.
+   */
+  function LodashWrapper(value, chainAll) {
+    this.__wrapped__ = value;
+    this.__actions__ = [];
+    this.__chain__ = !!chainAll;
+  }
+
+  LodashWrapper.prototype = baseCreate(lodash.prototype);
+  LodashWrapper.prototype.constructor = LodashWrapper;
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Used by `_.defaults` to customize its `_.assignIn` use.
+   *
+   * @private
+   * @param {*} objValue The destination value.
+   * @param {*} srcValue The source value.
+   * @param {string} key The key of the property to assign.
+   * @param {Object} object The parent object of `objValue`.
+   * @returns {*} Returns the value to assign.
+   */
+  function assignInDefaults(objValue, srcValue, key, object) {
+    if (objValue === undefined ||
+        (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+      return srcValue;
+    }
+    return objValue;
+  }
+
+  /**
+   * Assigns `value` to `key` of `object` if the existing value is not equivalent
+   * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+   * for equality comparisons.
+   *
+   * @private
+   * @param {Object} object The object to modify.
+   * @param {string} key The key of the property to assign.
+   * @param {*} value The value to assign.
+   */
+  function assignValue(object, key, value) {
+    var objValue = object[key];
+    if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+        (value === undefined && !(key in object))) {
+      object[key] = value;
+    }
+  }
+
+  /**
+   * The base implementation of `_.create` without support for assigning
+   * properties to the created object.
+   *
+   * @private
+   * @param {Object} prototype The object to inherit from.
+   * @returns {Object} Returns the new object.
+   */
+  function baseCreate(proto) {
+    return isObject(proto) ? objectCreate(proto) : {};
+  }
+
+  /**
+   * The base implementation of `_.delay` and `_.defer` which accepts an array
+   * of `func` arguments.
+   *
+   * @private
+   * @param {Function} func The function to delay.
+   * @param {number} wait The number of milliseconds to delay invocation.
+   * @param {Object} args The arguments to provide to `func`.
+   * @returns {number} Returns the timer id.
+   */
+  function baseDelay(func, wait, args) {
+    if (typeof func != 'function') {
+      throw new TypeError(FUNC_ERROR_TEXT);
+    }
+    return setTimeout(function() { func.apply(undefined, args); }, wait);
+  }
+
+  /**
+   * The base implementation of `_.forEach` without support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array|Object} Returns `collection`.
+   */
+  var baseEach = createBaseEach(baseForOwn);
+
+  /**
+   * The base implementation of `_.every` without support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if all elements pass the predicate check,
+   *  else `false`
+   */
+  function baseEvery(collection, predicate) {
+    var result = true;
+    baseEach(collection, function(value, index, collection) {
+      result = !!predicate(value, index, collection);
+      return result;
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of methods like `_.max` and `_.min` which accepts a
+   * `comparator` to determine the extremum value.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The iteratee invoked per iteration.
+   * @param {Function} comparator The comparator used to compare values.
+   * @returns {*} Returns the extremum value.
+   */
+  function baseExtremum(array, iteratee, comparator) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var value = array[index],
+          current = iteratee(value);
+
+      if (current != null && (computed === undefined
+            ? (current === current && !false)
+            : comparator(current, computed)
+          )) {
+        var computed = current,
+            result = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.filter` without support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {Array} Returns the new filtered array.
+   */
+  function baseFilter(collection, predicate) {
+    var result = [];
+    baseEach(collection, function(value, index, collection) {
+      if (predicate(value, index, collection)) {
+        result.push(value);
+      }
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.flatten` with support for restricting flattening.
+   *
+   * @private
+   * @param {Array} array The array to flatten.
+   * @param {number} depth The maximum recursion depth.
+   * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+   * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+   * @param {Array} [result=[]] The initial result value.
+   * @returns {Array} Returns the new flattened array.
+   */
+  function baseFlatten(array, depth, predicate, isStrict, result) {
+    var index = -1,
+        length = array.length;
+
+    predicate || (predicate = isFlattenable);
+    result || (result = []);
+
+    while (++index < length) {
+      var value = array[index];
+      if (depth > 0 && predicate(value)) {
+        if (depth > 1) {
+          // Recursively flatten arrays (susceptible to call stack limits).
+          baseFlatten(value, depth - 1, predicate, isStrict, result);
+        } else {
+          arrayPush(result, value);
+        }
+      } else if (!isStrict) {
+        result[result.length] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `baseForOwn` which iterates over `object`
+   * properties returned by `keysFunc` and invokes `iteratee` for each property.
+   * Iteratee functions may exit iteration early by explicitly returning `false`.
+   *
+   * @private
+   * @param {Object} object The object to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {Function} keysFunc The function to get the keys of `object`.
+   * @returns {Object} Returns `object`.
+   */
+  var baseFor = createBaseFor();
+
+  /**
+   * The base implementation of `_.forOwn` without support for iteratee shorthands.
+   *
+   * @private
+   * @param {Object} object The object to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Object} Returns `object`.
+   */
+  function baseForOwn(object, iteratee) {
+    return object && baseFor(object, iteratee, keys);
+  }
+
+  /**
+   * The base implementation of `_.functions` which creates an array of
+   * `object` function property names filtered from `props`.
+   *
+   * @private
+   * @param {Object} object The object to inspect.
+   * @param {Array} props The property names to filter.
+   * @returns {Array} Returns the new array of filtered property names.
+   */
+  function baseFunctions(object, props) {
+    return baseFilter(props, function(key) {
+      return isFunction(object[key]);
+    });
+  }
+
+  /**
+   * The base implementation of `_.gt` which doesn't coerce arguments to numbers.
+   *
+   * @private
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @returns {boolean} Returns `true` if `value` is greater than `other`,
+   *  else `false`.
+   */
+  function baseGt(value, other) {
+    return value > other;
+  }
+
+  /**
+   * The base implementation of `_.isEqual` which supports partial comparisons
+   * and tracks traversed objects.
+   *
+   * @private
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @param {Function} [customizer] The function to customize comparisons.
+   * @param {boolean} [bitmask] The bitmask of comparison flags.
+   *  The bitmask may be composed of the following flags:
+   *     1 - Unordered comparison
+   *     2 - Partial comparison
+   * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+   */
+  function baseIsEqual(value, other, customizer, bitmask, stack) {
+    if (value === other) {
+      return true;
+    }
+    if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+      return value !== value && other !== other;
+    }
+    return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
+  }
+
+  /**
+   * A specialized version of `baseIsEqual` for arrays and objects which performs
+   * deep comparisons and tracks traversed objects enabling objects with circular
+   * references to be compared.
+   *
+   * @private
+   * @param {Object} object The object to compare.
+   * @param {Object} other The other object to compare.
+   * @param {Function} equalFunc The function to determine equivalents of values.
+   * @param {Function} [customizer] The function to customize comparisons.
+   * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual`
+   *  for more details.
+   * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+   * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+   */
+  function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
+    var objIsArr = isArray(object),
+        othIsArr = isArray(other),
+        objTag = arrayTag,
+        othTag = arrayTag;
+
+    if (!objIsArr) {
+      objTag = objectToString.call(object);
+      objTag = objTag == argsTag ? objectTag : objTag;
+    }
+    if (!othIsArr) {
+      othTag = objectToString.call(other);
+      othTag = othTag == argsTag ? objectTag : othTag;
+    }
+    var objIsObj = objTag == objectTag && !isHostObject(object),
+        othIsObj = othTag == objectTag && !isHostObject(other),
+        isSameTag = objTag == othTag;
+
+    stack || (stack = []);
+    var stacked = find(stack, function(entry) {
+      return entry[0] === object;
+    });
+    if (stacked && stacked[1]) {
+      return stacked[1] == other;
+    }
+    stack.push([object, other]);
+    if (isSameTag && !objIsObj) {
+      var result = (objIsArr || isTypedArray(object))
+        ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)
+        : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);
+      stack.pop();
+      return result;
+    }
+    if (!(bitmask & PARTIAL_COMPARE_FLAG)) {
+      var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+          othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+      if (objIsWrapped || othIsWrapped) {
+        var objUnwrapped = objIsWrapped ? object.value() : object,
+            othUnwrapped = othIsWrapped ? other.value() : other;
+
+        var result = equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack);
+        stack.pop();
+        return result;
+      }
+    }
+    if (!isSameTag) {
+      return false;
+    }
+    var result = equalObjects(object, other, equalFunc, customizer, bitmask, stack);
+    stack.pop();
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.iteratee`.
+   *
+   * @private
+   * @param {*} [value=_.identity] The value to convert to an iteratee.
+   * @returns {Function} Returns the iteratee.
+   */
+  function baseIteratee(func) {
+    if (typeof func == 'function') {
+      return func;
+    }
+    if (func == null) {
+      return identity;
+    }
+    return (typeof func == 'object' ? baseMatches : baseProperty)(func);
+  }
+
+  /**
+   * The base implementation of `_.keys` which doesn't skip the constructor
+   * property of prototypes or treat sparse arrays as dense.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @returns {Array} Returns the array of property names.
+   */
+  function baseKeys(object) {
+    return nativeKeys(Object(object));
+  }
+
+  /**
+   * The base implementation of `_.keysIn` which doesn't skip the constructor
+   * property of prototypes or treat sparse arrays as dense.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @returns {Array} Returns the array of property names.
+   */
+  function baseKeysIn(object) {
+    object = object == null ? object : Object(object);
+
+    var result = [];
+    for (var key in object) {
+      result.push(key);
+    }
+    return result;
+  }
+
+  // Fallback for IE < 9 with es6-shim.
+  if (enumerate && !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf')) {
+    baseKeysIn = function(object) {
+      return iteratorToArray(enumerate(object));
+    };
+  }
+
+  /**
+   * The base implementation of `_.lt` which doesn't coerce arguments to numbers.
+   *
+   * @private
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @returns {boolean} Returns `true` if `value` is less than `other`,
+   *  else `false`.
+   */
+  function baseLt(value, other) {
+    return value < other;
+  }
+
+  /**
+   * The base implementation of `_.map` without support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the new mapped array.
+   */
+  function baseMap(collection, iteratee) {
+    var index = -1,
+        result = isArrayLike(collection) ? Array(collection.length) : [];
+
+    baseEach(collection, function(value, key, collection) {
+      result[++index] = iteratee(value, key, collection);
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.matches` which doesn't clone `source`.
+   *
+   * @private
+   * @param {Object} source The object of property values to match.
+   * @returns {Function} Returns the new function.
+   */
+  function baseMatches(source) {
+    var props = keys(source);
+    return function(object) {
+      var length = props.length;
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (length--) {
+        var key = props[length];
+        if (!(key in object &&
+              baseIsEqual(source[key], object[key], undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG)
+            )) {
+          return false;
+        }
+      }
+      return true;
+    };
+  }
+
+  /**
+   * The base implementation of `_.pick` without support for individual
+   * property identifiers.
+   *
+   * @private
+   * @param {Object} object The source object.
+   * @param {string[]} props The property identifiers to pick.
+   * @returns {Object} Returns the new object.
+   */
+  function basePick(object, props) {
+    object = Object(object);
+    return reduce(props, function(result, key) {
+      if (key in object) {
+        result[key] = object[key];
+      }
+      return result;
+    }, {});
+  }
+
+  /**
+   * The base implementation of `_.property` without support for deep paths.
+   *
+   * @private
+   * @param {string} key The key of the property to get.
+   * @returns {Function} Returns the new function.
+   */
+  function baseProperty(key) {
+    return function(object) {
+      return object == null ? undefined : object[key];
+    };
+  }
+
+  /**
+   * The base implementation of `_.slice` without an iteratee call guard.
+   *
+   * @private
+   * @param {Array} array The array to slice.
+   * @param {number} [start=0] The start position.
+   * @param {number} [end=array.length] The end position.
+   * @returns {Array} Returns the slice of `array`.
+   */
+  function baseSlice(array, start, end) {
+    var index = -1,
+        length = array.length;
+
+    if (start < 0) {
+      start = -start > length ? 0 : (length + start);
+    }
+    end = end > length ? length : end;
+    if (end < 0) {
+      end += length;
+    }
+    length = start > end ? 0 : ((end - start) >>> 0);
+    start >>>= 0;
+
+    var result = Array(length);
+    while (++index < length) {
+      result[index] = array[index + start];
+    }
+    return result;
+  }
+
+  /**
+   * Copies the values of `source` to `array`.
+   *
+   * @private
+   * @param {Array} source The array to copy values from.
+   * @param {Array} [array=[]] The array to copy values to.
+   * @returns {Array} Returns `array`.
+   */
+  function copyArray(source) {
+    return baseSlice(source, 0, source.length);
+  }
+
+  /**
+   * The base implementation of `_.some` without support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if any element passes the predicate check,
+   *  else `false`.
+   */
+  function baseSome(collection, predicate) {
+    var result;
+
+    baseEach(collection, function(value, index, collection) {
+      result = predicate(value, index, collection);
+      return !result;
+    });
+    return !!result;
+  }
+
+  /**
+   * The base implementation of `wrapperValue` which returns the result of
+   * performing a sequence of actions on the unwrapped `value`, where each
+   * successive action is supplied the return value of the previous.
+   *
+   * @private
+   * @param {*} value The unwrapped value.
+   * @param {Array} actions Actions to perform to resolve the unwrapped value.
+   * @returns {*} Returns the resolved value.
+   */
+  function baseWrapperValue(value, actions) {
+    var result = value;
+    return reduce(actions, function(result, action) {
+      return action.func.apply(action.thisArg, arrayPush([result], action.args));
+    }, result);
+  }
+
+  /**
+   * Compares values to sort them in ascending order.
+   *
+   * @private
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @returns {number} Returns the sort order indicator for `value`.
+   */
+  function compareAscending(value, other) {
+    if (value !== other) {
+      var valIsDefined = value !== undefined,
+          valIsNull = value === null,
+          valIsReflexive = value === value,
+          valIsSymbol = false;
+
+      var othIsDefined = other !== undefined,
+          othIsNull = other === null,
+          othIsReflexive = other === other,
+          othIsSymbol = false;
+
+      if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
+          (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
+          (valIsNull && othIsDefined && othIsReflexive) ||
+          (!valIsDefined && othIsReflexive) ||
+          !valIsReflexive) {
+        return 1;
+      }
+      if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
+          (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
+          (othIsNull && valIsDefined && valIsReflexive) ||
+          (!othIsDefined && valIsReflexive) ||
+          !othIsReflexive) {
+        return -1;
+      }
+    }
+    return 0;
+  }
+
+  /**
+   * Copies properties of `source` to `object`.
+   *
+   * @private
+   * @param {Object} source The object to copy properties from.
+   * @param {Array} props The property identifiers to copy.
+   * @param {Object} [object={}] The object to copy properties to.
+   * @param {Function} [customizer] The function to customize copied values.
+   * @returns {Object} Returns `object`.
+   */
+  function copyObject(source, props, object, customizer) {
+    object || (object = {});
+
+    var index = -1,
+        length = props.length;
+
+    while (++index < length) {
+      var key = props[index];
+
+      var newValue = customizer
+        ? customizer(object[key], source[key], key, object, source)
+        : source[key];
+
+      assignValue(object, key, newValue);
+    }
+    return object;
+  }
+
+  /**
+   * Creates a function like `_.assign`.
+   *
+   * @private
+   * @param {Function} assigner The function to assign values.
+   * @returns {Function} Returns the new assigner function.
+   */
+  function createAssigner(assigner) {
+    return rest(function(object, sources) {
+      var index = -1,
+          length = sources.length,
+          customizer = length > 1 ? sources[length - 1] : undefined;
+
+      customizer = typeof customizer == 'function'
+        ? (length--, customizer)
+        : undefined;
+
+      object = Object(object);
+      while (++index < length) {
+        var source = sources[index];
+        if (source) {
+          assigner(object, source, index, customizer);
+        }
+      }
+      return object;
+    });
+  }
+
+  /**
+   * Creates a `baseEach` or `baseEachRight` function.
+   *
+   * @private
+   * @param {Function} eachFunc The function to iterate over a collection.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {Function} Returns the new base function.
+   */
+  function createBaseEach(eachFunc, fromRight) {
+    return function(collection, iteratee) {
+      if (collection == null) {
+        return collection;
+      }
+      if (!isArrayLike(collection)) {
+        return eachFunc(collection, iteratee);
+      }
+      var length = collection.length,
+          index = fromRight ? length : -1,
+          iterable = Object(collection);
+
+      while ((fromRight ? index-- : ++index < length)) {
+        if (iteratee(iterable[index], index, iterable) === false) {
+          break;
+        }
+      }
+      return collection;
+    };
+  }
+
+  /**
+   * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+   *
+   * @private
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {Function} Returns the new base function.
+   */
+  function createBaseFor(fromRight) {
+    return function(object, iteratee, keysFunc) {
+      var index = -1,
+          iterable = Object(object),
+          props = keysFunc(object),
+          length = props.length;
+
+      while (length--) {
+        var key = props[fromRight ? length : ++index];
+        if (iteratee(iterable[key], key, iterable) === false) {
+          break;
+        }
+      }
+      return object;
+    };
+  }
+
+  /**
+   * Creates a function that produces an instance of `Ctor` regardless of
+   * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+   *
+   * @private
+   * @param {Function} Ctor The constructor to wrap.
+   * @returns {Function} Returns the new wrapped function.
+   */
+  function createCtorWrapper(Ctor) {
+    return function() {
+      // Use a `switch` statement to work with class constructors. See
+      // http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+      // for more details.
+      var args = arguments;
+      var thisBinding = baseCreate(Ctor.prototype),
+          result = Ctor.apply(thisBinding, args);
+
+      // Mimic the constructor's `return` behavior.
+      // See https://es5.github.io/#x13.2.2 for more details.
+      return isObject(result) ? result : thisBinding;
+    };
+  }
+
+  /**
+   * Creates a function that wraps `func` to invoke it with the `this` binding
+   * of `thisArg` and `partials` prepended to the arguments it receives.
+   *
+   * @private
+   * @param {Function} func The function to wrap.
+   * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+   *  for more details.
+   * @param {*} thisArg The `this` binding of `func`.
+   * @param {Array} partials The arguments to prepend to those provided to
+   *  the new function.
+   * @returns {Function} Returns the new wrapped function.
+   */
+  function createPartialWrapper(func, bitmask, thisArg, partials) {
+    if (typeof func != 'function') {
+      throw new TypeError(FUNC_ERROR_TEXT);
+    }
+    var isBind = bitmask & BIND_FLAG,
+        Ctor = createCtorWrapper(func);
+
+    function wrapper() {
+      var argsIndex = -1,
+          argsLength = arguments.length,
+          leftIndex = -1,
+          leftLength = partials.length,
+          args = Array(leftLength + argsLength),
+          fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+      while (++leftIndex < leftLength) {
+        args[leftIndex] = partials[leftIndex];
+      }
+      while (argsLength--) {
+        args[leftIndex++] = arguments[++argsIndex];
+      }
+      return fn.apply(isBind ? thisArg : this, args);
+    }
+    return wrapper;
+  }
+
+  /**
+   * A specialized version of `baseIsEqualDeep` for arrays with support for
+   * partial deep comparisons.
+   *
+   * @private
+   * @param {Array} array The array to compare.
+   * @param {Array} other The other array to compare.
+   * @param {Function} equalFunc The function to determine equivalents of values.
+   * @param {Function} customizer The function to customize comparisons.
+   * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+   *  for more details.
+   * @param {Object} stack Tracks traversed `array` and `other` objects.
+   * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+   */
+  function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
+    var index = -1,
+        isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+        isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
+        arrLength = array.length,
+        othLength = other.length;
+
+    if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+      return false;
+    }
+    var result = true;
+
+    // Ignore non-index properties.
+    while (++index < arrLength) {
+      var arrValue = array[index],
+          othValue = other[index];
+
+      var compared;
+      if (compared !== undefined) {
+        if (compared) {
+          continue;
+        }
+        result = false;
+        break;
+      }
+      // Recursively compare arrays (susceptible to call stack limits).
+      if (isUnordered) {
+        if (!baseSome(other, function(othValue) {
+              return arrValue === othValue ||
+                equalFunc(arrValue, othValue, customizer, bitmask, stack);
+            })) {
+          result = false;
+          break;
+        }
+      } else if (!(
+            arrValue === othValue ||
+              equalFunc(arrValue, othValue, customizer, bitmask, stack)
+          )) {
+        result = false;
+        break;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `baseIsEqualDeep` for comparing objects of
+   * the same `toStringTag`.
+   *
+   * **Note:** This function only supports comparing values with tags of
+   * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+   *
+   * @private
+   * @param {Object} object The object to compare.
+   * @param {Object} other The other object to compare.
+   * @param {string} tag The `toStringTag` of the objects to compare.
+   * @param {Function} equalFunc The function to determine equivalents of values.
+   * @param {Function} customizer The function to customize comparisons.
+   * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+   *  for more details.
+   * @param {Object} stack Tracks traversed `object` and `other` objects.
+   * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+   */
+  function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) {
+    switch (tag) {
+
+      case boolTag:
+      case dateTag:
+        // 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 +object == +other;
+
+      case errorTag:
+        return object.name == other.name && object.message == other.message;
+
+      case numberTag:
+        // Treat `NaN` vs. `NaN` as equal.
+        return (object != +object) ? other != +other : object == +other;
+
+      case regexpTag:
+      case stringTag:
+        // Coerce regexes to strings and treat strings, primitives and objects,
+        // as equal. See http://www.ecma-international.org/ecma-262/6.0/#sec-regexp.prototype.tostring
+        // for more details.
+        return object == (other + '');
+
+    }
+    return false;
+  }
+
+  /**
+   * A specialized version of `baseIsEqualDeep` for objects with support for
+   * partial deep comparisons.
+   *
+   * @private
+   * @param {Object} object The object to compare.
+   * @param {Object} other The other object to compare.
+   * @param {Function} equalFunc The function to determine equivalents of values.
+   * @param {Function} customizer The function to customize comparisons.
+   * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+   *  for more details.
+   * @param {Object} stack Tracks traversed `object` and `other` objects.
+   * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+   */
+  function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
+    var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+        objProps = keys(object),
+        objLength = objProps.length,
+        othProps = keys(other),
+        othLength = othProps.length;
+
+    if (objLength != othLength && !isPartial) {
+      return false;
+    }
+    var index = objLength;
+    while (index--) {
+      var key = objProps[index];
+      if (!(isPartial ? key in other : hasOwnProperty.call(other, key))) {
+        return false;
+      }
+    }
+    var result = true;
+
+    var skipCtor = isPartial;
+    while (++index < objLength) {
+      key = objProps[index];
+      var objValue = object[key],
+          othValue = other[key];
+
+      var compared;
+      // Recursively compare objects (susceptible to call stack limits).
+      if (!(compared === undefined
+            ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
+            : compared
+          )) {
+        result = false;
+        break;
+      }
+      skipCtor || (skipCtor = key == 'constructor');
+    }
+    if (result && !skipCtor) {
+      var objCtor = object.constructor,
+          othCtor = other.constructor;
+
+      // Non `Object` object instances with different constructors are not equal.
+      if (objCtor != othCtor &&
+          ('constructor' in object && 'constructor' in other) &&
+          !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+            typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+        result = false;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Gets the "length" property value of `object`.
+   *
+   * **Note:** This function is used to avoid a
+   * [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) that affects
+   * Safari on at least iOS 8.1-8.3 ARM64.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @returns {*} Returns the "length" value.
+   */
+  var getLength = baseProperty('length');
+
+  /**
+   * Creates an array of index keys for `object` values of arrays,
+   * `arguments` objects, and strings, otherwise `null` is returned.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @returns {Array|null} Returns index keys, else `null`.
+   */
+  function indexKeys(object) {
+    var length = object ? object.length : undefined;
+    if (isLength(length) &&
+        (isArray(object) || isString(object) || isArguments(object))) {
+      return baseTimes(length, String);
+    }
+    return null;
+  }
+
+  /**
+   * Checks if `value` is a flattenable `arguments` object or array.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+   */
+  function isFlattenable(value) {
+    return isArrayLikeObject(value) && (isArray(value) || isArguments(value));
+  }
+
+  /**
+   * Checks if `value` is a valid array-like index.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+   * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+   */
+  function isIndex(value, length) {
+    length = length == null ? MAX_SAFE_INTEGER : length;
+    return !!length &&
+      (typeof value == 'number' || reIsUint.test(value)) &&
+      (value > -1 && value % 1 == 0 && value < length);
+  }
+
+  /**
+   * Checks if `value` is likely a prototype object.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+   */
+  function isPrototype(value) {
+    var Ctor = value && value.constructor,
+        proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+    return value === proto;
+  }
+
+  /**
+   * Converts `value` to a string key if it's not a string or symbol.
+   *
+   * @private
+   * @param {*} value The value to inspect.
+   * @returns {string|symbol} Returns the key.
+   */
+  var toKey = String;
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Creates an array with all falsey values removed. The values `false`, `null`,
+   * `0`, `""`, `undefined`, and `NaN` are falsey.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Array
+   * @param {Array} array The array to compact.
+   * @returns {Array} Returns the new array of filtered values.
+   * @example
+   *
+   * _.compact([0, 1, false, 2, '', 3]);
+   * // => [1, 2, 3]
+   */
+  function compact(array) {
+    return baseFilter(array, Boolean);
+  }
+
+  /**
+   * Creates a new array concatenating `array` with any additional arrays
+   * and/or values.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Array
+   * @param {Array} array The array to concatenate.
+   * @param {...*} [values] The values to concatenate.
+   * @returns {Array} Returns the new concatenated array.
+   * @example
+   *
+   * var array = [1];
+   * var other = _.concat(array, 2, [3], [[4]]);
+   *
+   * console.log(other);
+   * // => [1, 2, 3, [4]]
+   *
+   * console.log(array);
+   * // => [1]
+   */
+  function concat() {
+    var length = arguments.length,
+        array = castArray(arguments[0]);
+
+    if (length < 2) {
+      return length ? copyArray(array) : [];
+    }
+    var args = Array(length - 1);
+    while (length--) {
+      args[length - 1] = arguments[length];
+    }
+    return arrayConcat(array, baseFlatten(args, 1));
+  }
+
+  /**
+   * Flattens `array` a single level deep.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Array
+   * @param {Array} array The array to flatten.
+   * @returns {Array} Returns the new flattened array.
+   * @example
+   *
+   * _.flatten([1, [2, [3, [4]], 5]]);
+   * // => [1, 2, [3, [4]], 5]
+   */
+  function flatten(array) {
+    var length = array ? array.length : 0;
+    return length ? baseFlatten(array, 1) : [];
+  }
+
+  /**
+   * Recursively flattens `array`.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Array
+   * @param {Array} array The array to flatten.
+   * @returns {Array} Returns the new flattened array.
+   * @example
+   *
+   * _.flattenDeep([1, [2, [3, [4]], 5]]);
+   * // => [1, 2, 3, 4, 5]
+   */
+  function flattenDeep(array) {
+    var length = array ? array.length : 0;
+    return length ? baseFlatten(array, INFINITY) : [];
+  }
+
+  /**
+   * Gets the first element of `array`.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @alias first
+   * @category Array
+   * @param {Array} array The array to query.
+   * @returns {*} Returns the first element of `array`.
+   * @example
+   *
+   * _.head([1, 2, 3]);
+   * // => 1
+   *
+   * _.head([]);
+   * // => undefined
+   */
+  function head(array) {
+    return (array && array.length) ? array[0] : undefined;
+  }
+
+  /**
+   * Gets the index at which the first occurrence of `value` is found in `array`
+   * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+   * for equality comparisons. If `fromIndex` is negative, it's used as the
+   * offset from the end of `array`.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Array
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} [fromIndex=0] The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   * @example
+   *
+   * _.indexOf([1, 2, 1, 2], 2);
+   * // => 1
+   *
+   * // Search from the `fromIndex`.
+   * _.indexOf([1, 2, 1, 2], 2, 2);
+   * // => 3
+   */
+  function indexOf(array, value, fromIndex) {
+    var length = array ? array.length : 0;
+    if (typeof fromIndex == 'number') {
+      fromIndex = fromIndex < 0 ? nativeMax(length + fromIndex, 0) : fromIndex;
+    } else {
+      fromIndex = 0;
+    }
+    var index = (fromIndex || 0) - 1,
+        isReflexive = value === value;
+
+    while (++index < length) {
+      var other = array[index];
+      if ((isReflexive ? other === value : other !== other)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Gets the last element of `array`.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Array
+   * @param {Array} array The array to query.
+   * @returns {*} Returns the last element of `array`.
+   * @example
+   *
+   * _.last([1, 2, 3]);
+   * // => 3
+   */
+  function last(array) {
+    var length = array ? array.length : 0;
+    return length ? array[length - 1] : undefined;
+  }
+
+  /**
+   * Creates a slice of `array` from `start` up to, but not including, `end`.
+   *
+   * **Note:** This method is used instead of
+   * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+   * returned.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Array
+   * @param {Array} array The array to slice.
+   * @param {number} [start=0] The start position.
+   * @param {number} [end=array.length] The end position.
+   * @returns {Array} Returns the slice of `array`.
+   */
+  function slice(array, start, end) {
+    var length = array ? array.length : 0;
+    start = start == null ? 0 : +start;
+    end = end === undefined ? length : +end;
+    return length ? baseSlice(array, start, end) : [];
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+   * chain sequences enabled. The result of such sequences must be unwrapped
+   * with `_#value`.
+   *
+   * @static
+   * @memberOf _
+   * @since 1.3.0
+   * @category Seq
+   * @param {*} value The value to wrap.
+   * @returns {Object} Returns the new `lodash` wrapper instance.
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'barney',  'age': 36 },
+   *   { 'user': 'fred',    'age': 40 },
+   *   { 'user': 'pebbles', 'age': 1 }
+   * ];
+   *
+   * var youngest = _
+   *   .chain(users)
+   *   .sortBy('age')
+   *   .map(function(o) {
+   *     return o.user + ' is ' + o.age;
+   *   })
+   *   .head()
+   *   .value();
+   * // => 'pebbles is 1'
+   */
+  function chain(value) {
+    var result = lodash(value);
+    result.__chain__ = true;
+    return result;
+  }
+
+  /**
+   * This method invokes `interceptor` and returns `value`. The interceptor
+   * is invoked with one argument; (value). The purpose of this method is to
+   * "tap into" a method chain sequence in order to modify intermediate results.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Seq
+   * @param {*} value The value to provide to `interceptor`.
+   * @param {Function} interceptor The function to invoke.
+   * @returns {*} Returns `value`.
+   * @example
+   *
+   * _([1, 2, 3])
+   *  .tap(function(array) {
+   *    // Mutate input array.
+   *    array.pop();
+   *  })
+   *  .reverse()
+   *  .value();
+   * // => [2, 1]
+   */
+  function tap(value, interceptor) {
+    interceptor(value);
+    return value;
+  }
+
+  /**
+   * This method is like `_.tap` except that it returns the result of `interceptor`.
+   * The purpose of this method is to "pass thru" values replacing intermediate
+   * results in a method chain sequence.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Seq
+   * @param {*} value The value to provide to `interceptor`.
+   * @param {Function} interceptor The function to invoke.
+   * @returns {*} Returns the result of `interceptor`.
+   * @example
+   *
+   * _('  abc  ')
+   *  .chain()
+   *  .trim()
+   *  .thru(function(value) {
+   *    return [value];
+   *  })
+   *  .value();
+   * // => ['abc']
+   */
+  function thru(value, interceptor) {
+    return interceptor(value);
+  }
+
+  /**
+   * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+   *
+   * @name chain
+   * @memberOf _
+   * @since 0.1.0
+   * @category Seq
+   * @returns {Object} Returns the new `lodash` wrapper instance.
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'barney', 'age': 36 },
+   *   { 'user': 'fred',   'age': 40 }
+   * ];
+   *
+   * // A sequence without explicit chaining.
+   * _(users).head();
+   * // => { 'user': 'barney', 'age': 36 }
+   *
+   * // A sequence with explicit chaining.
+   * _(users)
+   *   .chain()
+   *   .head()
+   *   .pick('user')
+   *   .value();
+   * // => { 'user': 'barney' }
+   */
+  function wrapperChain() {
+    return chain(this);
+  }
+
+  /**
+   * Executes the chain sequence to resolve the unwrapped value.
+   *
+   * @name value
+   * @memberOf _
+   * @since 0.1.0
+   * @alias toJSON, valueOf
+   * @category Seq
+   * @returns {*} Returns the resolved unwrapped value.
+   * @example
+   *
+   * _([1, 2, 3]).value();
+   * // => [1, 2, 3]
+   */
+  function wrapperValue() {
+    return baseWrapperValue(this.__wrapped__, this.__actions__);
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Checks if `predicate` returns truthy for **all** elements of `collection`.
+   * Iteration is stopped once `predicate` returns falsey. The predicate is
+   * invoked with three arguments: (value, index|key, collection).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Array|Function|Object|string} [predicate=_.identity]
+   *  The function invoked per iteration.
+   * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+   * @returns {boolean} Returns `true` if all elements pass the predicate check,
+   *  else `false`.
+   * @example
+   *
+   * _.every([true, 1, null, 'yes'], Boolean);
+   * // => false
+   *
+   * var users = [
+   *   { 'user': 'barney', 'age': 36, 'active': false },
+   *   { 'user': 'fred',   'age': 40, 'active': false }
+   * ];
+   *
+   * // The `_.matches` iteratee shorthand.
+   * _.every(users, { 'user': 'barney', 'active': false });
+   * // => false
+   *
+   * // The `_.matchesProperty` iteratee shorthand.
+   * _.every(users, ['active', false]);
+   * // => true
+   *
+   * // The `_.property` iteratee shorthand.
+   * _.every(users, 'active');
+   * // => false
+   */
+  function every(collection, predicate, guard) {
+    predicate = guard ? undefined : predicate;
+    return baseEvery(collection, baseIteratee(predicate));
+  }
+
+  /**
+   * Iterates over elements of `collection`, returning an array of all elements
+   * `predicate` returns truthy for. The predicate is invoked with three
+   * arguments: (value, index|key, collection).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Array|Function|Object|string} [predicate=_.identity]
+   *  The function invoked per iteration.
+   * @returns {Array} Returns the new filtered array.
+   * @see _.reject
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'barney', 'age': 36, 'active': true },
+   *   { 'user': 'fred',   'age': 40, 'active': false }
+   * ];
+   *
+   * _.filter(users, function(o) { return !o.active; });
+   * // => objects for ['fred']
+   *
+   * // The `_.matches` iteratee shorthand.
+   * _.filter(users, { 'age': 36, 'active': true });
+   * // => objects for ['barney']
+   *
+   * // The `_.matchesProperty` iteratee shorthand.
+   * _.filter(users, ['active', false]);
+   * // => objects for ['fred']
+   *
+   * // The `_.property` iteratee shorthand.
+   * _.filter(users, 'active');
+   * // => objects for ['barney']
+   */
+  function filter(collection, predicate) {
+    return baseFilter(collection, baseIteratee(predicate));
+  }
+
+  /**
+   * Iterates over elements of `collection`, returning the first element
+   * `predicate` returns truthy for. The predicate is invoked with three
+   * arguments: (value, index|key, collection).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to search.
+   * @param {Array|Function|Object|string} [predicate=_.identity]
+   *  The function invoked per iteration.
+   * @returns {*} Returns the matched element, else `undefined`.
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'barney',  'age': 36, 'active': true },
+   *   { 'user': 'fred',    'age': 40, 'active': false },
+   *   { 'user': 'pebbles', 'age': 1,  'active': true }
+   * ];
+   *
+   * _.find(users, function(o) { return o.age < 40; });
+   * // => object for 'barney'
+   *
+   * // The `_.matches` iteratee shorthand.
+   * _.find(users, { 'age': 1, 'active': true });
+   * // => object for 'pebbles'
+   *
+   * // The `_.matchesProperty` iteratee shorthand.
+   * _.find(users, ['active', false]);
+   * // => object for 'fred'
+   *
+   * // The `_.property` iteratee shorthand.
+   * _.find(users, 'active');
+   * // => object for 'barney'
+   */
+  function find(collection, predicate) {
+    return baseFind(collection, baseIteratee(predicate), baseEach);
+  }
+
+  /**
+   * Iterates over elements of `collection` and invokes `iteratee` for each element.
+   * The iteratee is invoked with three arguments: (value, index|key, collection).
+   * Iteratee functions may exit iteration early by explicitly returning `false`.
+   *
+   * **Note:** As with other "Collections" methods, objects with a "length"
+   * property are iterated like arrays. To avoid this behavior use `_.forIn`
+   * or `_.forOwn` for object iteration.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @alias each
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+   * @returns {Array|Object} Returns `collection`.
+   * @see _.forEachRight
+   * @example
+   *
+   * _([1, 2]).forEach(function(value) {
+   *   console.log(value);
+   * });
+   * // => Logs `1` then `2`.
+   *
+   * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+   *   console.log(key);
+   * });
+   * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+   */
+  function forEach(collection, iteratee) {
+    return baseEach(collection, baseIteratee(iteratee));
+  }
+
+  /**
+   * Creates an array of values by running each element in `collection` thru
+   * `iteratee`. The iteratee is invoked with three arguments:
+   * (value, index|key, collection).
+   *
+   * Many lodash methods are guarded to work as iteratees for methods like
+   * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+   *
+   * The guarded methods are:
+   * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+   * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+   * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+   * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Array|Function|Object|string} [iteratee=_.identity]
+   *  The function invoked per iteration.
+   * @returns {Array} Returns the new mapped array.
+   * @example
+   *
+   * function square(n) {
+   *   return n * n;
+   * }
+   *
+   * _.map([4, 8], square);
+   * // => [16, 64]
+   *
+   * _.map({ 'a': 4, 'b': 8 }, square);
+   * // => [16, 64] (iteration order is not guaranteed)
+   *
+   * var users = [
+   *   { 'user': 'barney' },
+   *   { 'user': 'fred' }
+   * ];
+   *
+   * // The `_.property` iteratee shorthand.
+   * _.map(users, 'user');
+   * // => ['barney', 'fred']
+   */
+  function map(collection, iteratee) {
+    return baseMap(collection, baseIteratee(iteratee));
+  }
+
+  /**
+   * Reduces `collection` to a value which is the accumulated result of running
+   * each element in `collection` thru `iteratee`, where each successive
+   * invocation is supplied the return value of the previous. If `accumulator`
+   * is not given, the first element of `collection` is used as the initial
+   * value. The iteratee is invoked with four arguments:
+   * (accumulator, value, index|key, collection).
+   *
+   * Many lodash methods are guarded to work as iteratees for methods like
+   * `_.reduce`, `_.reduceRight`, and `_.transform`.
+   *
+   * The guarded methods are:
+   * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+   * and `sortBy`
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @returns {*} Returns the accumulated value.
+   * @see _.reduceRight
+   * @example
+   *
+   * _.reduce([1, 2], function(sum, n) {
+   *   return sum + n;
+   * }, 0);
+   * // => 3
+   *
+   * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+   *   (result[value] || (result[value] = [])).push(key);
+   *   return result;
+   * }, {});
+   * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+   */
+  function reduce(collection, iteratee, accumulator) {
+    return baseReduce(collection, baseIteratee(iteratee), accumulator, arguments.length < 3, baseEach);
+  }
+
+  /**
+   * Gets the size of `collection` by returning its length for array-like
+   * values or the number of own enumerable string keyed properties for objects.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to inspect.
+   * @returns {number} Returns the collection size.
+   * @example
+   *
+   * _.size([1, 2, 3]);
+   * // => 3
+   *
+   * _.size({ 'a': 1, 'b': 2 });
+   * // => 2
+   *
+   * _.size('pebbles');
+   * // => 7
+   */
+  function size(collection) {
+    if (collection == null) {
+      return 0;
+    }
+    collection = isArrayLike(collection) ? collection : keys(collection);
+    return collection.length;
+  }
+
+  /**
+   * Checks if `predicate` returns truthy for **any** element of `collection`.
+   * Iteration is stopped once `predicate` returns truthy. The predicate is
+   * invoked with three arguments: (value, index|key, collection).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Array|Function|Object|string} [predicate=_.identity]
+   *  The function invoked per iteration.
+   * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+   * @returns {boolean} Returns `true` if any element passes the predicate check,
+   *  else `false`.
+   * @example
+   *
+   * _.some([null, 0, 'yes', false], Boolean);
+   * // => true
+   *
+   * var users = [
+   *   { 'user': 'barney', 'active': true },
+   *   { 'user': 'fred',   'active': false }
+   * ];
+   *
+   * // The `_.matches` iteratee shorthand.
+   * _.some(users, { 'user': 'barney', 'active': false });
+   * // => false
+   *
+   * // The `_.matchesProperty` iteratee shorthand.
+   * _.some(users, ['active', false]);
+   * // => true
+   *
+   * // The `_.property` iteratee shorthand.
+   * _.some(users, 'active');
+   * // => true
+   */
+  function some(collection, predicate, guard) {
+    predicate = guard ? undefined : predicate;
+    return baseSome(collection, baseIteratee(predicate));
+  }
+
+  /**
+   * Creates an array of elements, sorted in ascending order by the results of
+   * running each element in a collection thru each iteratee. This method
+   * performs a stable sort, that is, it preserves the original sort order of
+   * equal elements. The iteratees are invoked with one argument: (value).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Collection
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+   *  [iteratees=[_.identity]] The iteratees to sort by.
+   * @returns {Array} Returns the new sorted array.
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'fred',   'age': 48 },
+   *   { 'user': 'barney', 'age': 36 },
+   *   { 'user': 'fred',   'age': 40 },
+   *   { 'user': 'barney', 'age': 34 }
+   * ];
+   *
+   * _.sortBy(users, function(o) { return o.user; });
+   * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+   *
+   * _.sortBy(users, ['user', 'age']);
+   * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+   *
+   * _.sortBy(users, 'user', function(o) {
+   *   return Math.floor(o.age / 10);
+   * });
+   * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+   */
+  function sortBy(collection, iteratee) {
+    var index = 0;
+    iteratee = baseIteratee(iteratee);
+
+    return baseMap(baseMap(collection, function(value, key, collection) {
+      return { 'value': value, 'index': index++, 'criteria': iteratee(value, key, collection) };
+    }).sort(function(object, other) {
+      return compareAscending(object.criteria, other.criteria) || (object.index - other.index);
+    }), baseProperty('value'));
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Creates a function that invokes `func`, with the `this` binding and arguments
+   * of the created function, while it's called less than `n` times. Subsequent
+   * calls to the created function return the result of the last `func` invocation.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Function
+   * @param {number} n The number of calls at which `func` is no longer invoked.
+   * @param {Function} func The function to restrict.
+   * @returns {Function} Returns the new restricted function.
+   * @example
+   *
+   * jQuery(element).on('click', _.before(5, addContactToList));
+   * // => allows adding up to 4 contacts to the list
+   */
+  function before(n, func) {
+    var result;
+    if (typeof func != 'function') {
+      throw new TypeError(FUNC_ERROR_TEXT);
+    }
+    n = toInteger(n);
+    return function() {
+      if (--n > 0) {
+        result = func.apply(this, arguments);
+      }
+      if (n <= 1) {
+        func = undefined;
+      }
+      return result;
+    };
+  }
+
+  /**
+   * Creates a function that invokes `func` with the `this` binding of `thisArg`
+   * and `partials` prepended to the arguments it receives.
+   *
+   * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+   * may be used as a placeholder for partially applied arguments.
+   *
+   * **Note:** Unlike native `Function#bind` this method doesn't set the "length"
+   * property of bound functions.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Function
+   * @param {Function} func The function to bind.
+   * @param {*} thisArg The `this` binding of `func`.
+   * @param {...*} [partials] The arguments to be partially applied.
+   * @returns {Function} Returns the new bound function.
+   * @example
+   *
+   * var greet = function(greeting, punctuation) {
+   *   return greeting + ' ' + this.user + punctuation;
+   * };
+   *
+   * var object = { 'user': 'fred' };
+   *
+   * var bound = _.bind(greet, object, 'hi');
+   * bound('!');
+   * // => 'hi fred!'
+   *
+   * // Bound with placeholders.
+   * var bound = _.bind(greet, object, _, '!');
+   * bound('hi');
+   * // => 'hi fred!'
+   */
+  var bind = rest(function(func, thisArg, partials) {
+    return createPartialWrapper(func, BIND_FLAG | PARTIAL_FLAG, thisArg, partials);
+  });
+
+  /**
+   * Defers invoking the `func` until the current call stack has cleared. Any
+   * additional arguments are provided to `func` when it's invoked.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Function
+   * @param {Function} func The function to defer.
+   * @param {...*} [args] The arguments to invoke `func` with.
+   * @returns {number} Returns the timer id.
+   * @example
+   *
+   * _.defer(function(text) {
+   *   console.log(text);
+   * }, 'deferred');
+   * // => Logs 'deferred' after one or more milliseconds.
+   */
+  var defer = rest(function(func, args) {
+    return baseDelay(func, 1, args);
+  });
+
+  /**
+   * Invokes `func` after `wait` milliseconds. Any additional arguments are
+   * provided to `func` when it's invoked.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Function
+   * @param {Function} func The function to delay.
+   * @param {number} wait The number of milliseconds to delay invocation.
+   * @param {...*} [args] The arguments to invoke `func` with.
+   * @returns {number} Returns the timer id.
+   * @example
+   *
+   * _.delay(function(text) {
+   *   console.log(text);
+   * }, 1000, 'later');
+   * // => Logs 'later' after one second.
+   */
+  var delay = rest(function(func, wait, args) {
+    return baseDelay(func, toNumber(wait) || 0, args);
+  });
+
+  /**
+   * Creates a function that negates the result of the predicate `func`. The
+   * `func` predicate is invoked with the `this` binding and arguments of the
+   * created function.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Function
+   * @param {Function} predicate The predicate to negate.
+   * @returns {Function} Returns the new function.
+   * @example
+   *
+   * function isEven(n) {
+   *   return n % 2 == 0;
+   * }
+   *
+   * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+   * // => [1, 3, 5]
+   */
+  function negate(predicate) {
+    if (typeof predicate != 'function') {
+      throw new TypeError(FUNC_ERROR_TEXT);
+    }
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  }
+
+  /**
+   * Creates a function that is restricted to invoking `func` once. Repeat calls
+   * to the function return the value of the first invocation. The `func` is
+   * invoked with the `this` binding and arguments of the created function.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Function
+   * @param {Function} func The function to restrict.
+   * @returns {Function} Returns the new restricted function.
+   * @example
+   *
+   * var initialize = _.once(createApplication);
+   * initialize();
+   * initialize();
+   * // `initialize` invokes `createApplication` once
+   */
+  function once(func) {
+    return before(2, func);
+  }
+
+  /**
+   * Creates a function that invokes `func` with the `this` binding of the
+   * created function and arguments from `start` and beyond provided as
+   * an array.
+   *
+   * **Note:** This method is based on the
+   * [rest parameter](https://mdn.io/rest_parameters).
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Function
+   * @param {Function} func The function to apply a rest parameter to.
+   * @param {number} [start=func.length-1] The start position of the rest parameter.
+   * @returns {Function} Returns the new function.
+   * @example
+   *
+   * var say = _.rest(function(what, names) {
+   *   return what + ' ' + _.initial(names).join(', ') +
+   *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+   * });
+   *
+   * say('hello', 'fred', 'barney', 'pebbles');
+   * // => 'hello fred, barney, & pebbles'
+   */
+  function rest(func, start) {
+    if (typeof func != 'function') {
+      throw new TypeError(FUNC_ERROR_TEXT);
+    }
+    start = nativeMax(start === undefined ? (func.length - 1) : toInteger(start), 0);
+    return function() {
+      var args = arguments,
+          index = -1,
+          length = nativeMax(args.length - start, 0),
+          array = Array(length);
+
+      while (++index < length) {
+        array[index] = args[start + index];
+      }
+      var otherArgs = Array(start + 1);
+      index = -1;
+      while (++index < start) {
+        otherArgs[index] = args[index];
+      }
+      otherArgs[start] = array;
+      return func.apply(this, otherArgs);
+    };
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Casts `value` as an array if it's not one.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.4.0
+   * @category Lang
+   * @param {*} value The value to inspect.
+   * @returns {Array} Returns the cast array.
+   * @example
+   *
+   * _.castArray(1);
+   * // => [1]
+   *
+   * _.castArray({ 'a': 1 });
+   * // => [{ 'a': 1 }]
+   *
+   * _.castArray('abc');
+   * // => ['abc']
+   *
+   * _.castArray(null);
+   * // => [null]
+   *
+   * _.castArray(undefined);
+   * // => [undefined]
+   *
+   * _.castArray();
+   * // => []
+   *
+   * var array = [1, 2, 3];
+   * console.log(_.castArray(array) === array);
+   * // => true
+   */
+  function castArray() {
+    if (!arguments.length) {
+      return [];
+    }
+    var value = arguments[0];
+    return isArray(value) ? value : [value];
+  }
+
+  /**
+   * Creates a shallow clone of `value`.
+   *
+   * **Note:** This method is loosely based on the
+   * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+   * and supports cloning arrays, array buffers, booleans, date objects, maps,
+   * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+   * arrays. The own enumerable properties of `arguments` objects are cloned
+   * as plain objects. An empty object is returned for uncloneable values such
+   * as error objects, functions, DOM nodes, and WeakMaps.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to clone.
+   * @returns {*} Returns the cloned value.
+   * @see _.cloneDeep
+   * @example
+   *
+   * var objects = [{ 'a': 1 }, { 'b': 2 }];
+   *
+   * var shallow = _.clone(objects);
+   * console.log(shallow[0] === objects[0]);
+   * // => true
+   */
+  function clone(value) {
+    if (!isObject(value)) {
+      return value;
+    }
+    return isArray(value) ? copyArray(value) : copyObject(value, keys(value));
+  }
+
+  /**
+   * Performs a
+   * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+   * comparison between two values to determine if they are equivalent.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+   * @example
+   *
+   * var object = { 'user': 'fred' };
+   * var other = { 'user': 'fred' };
+   *
+   * _.eq(object, object);
+   * // => true
+   *
+   * _.eq(object, other);
+   * // => false
+   *
+   * _.eq('a', 'a');
+   * // => true
+   *
+   * _.eq('a', Object('a'));
+   * // => false
+   *
+   * _.eq(NaN, NaN);
+   * // => true
+   */
+  function eq(value, other) {
+    return value === other || (value !== value && other !== other);
+  }
+
+  /**
+   * Checks if `value` is likely an `arguments` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isArguments(function() { return arguments; }());
+   * // => true
+   *
+   * _.isArguments([1, 2, 3]);
+   * // => false
+   */
+  function isArguments(value) {
+    // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+    return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+      (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+  }
+
+  /**
+   * Checks if `value` is classified as an `Array` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @type {Function}
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isArray([1, 2, 3]);
+   * // => true
+   *
+   * _.isArray(document.body.children);
+   * // => false
+   *
+   * _.isArray('abc');
+   * // => false
+   *
+   * _.isArray(_.noop);
+   * // => false
+   */
+  var isArray = Array.isArray;
+
+  /**
+   * Checks if `value` is array-like. A value is considered array-like if it's
+   * not a function and has a `value.length` that's an integer greater than or
+   * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+   * @example
+   *
+   * _.isArrayLike([1, 2, 3]);
+   * // => true
+   *
+   * _.isArrayLike(document.body.children);
+   * // => true
+   *
+   * _.isArrayLike('abc');
+   * // => true
+   *
+   * _.isArrayLike(_.noop);
+   * // => false
+   */
+  function isArrayLike(value) {
+    return value != null && isLength(getLength(value)) && !isFunction(value);
+  }
+
+  /**
+   * This method is like `_.isArrayLike` except that it also checks if `value`
+   * is an object.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is an array-like object,
+   *  else `false`.
+   * @example
+   *
+   * _.isArrayLikeObject([1, 2, 3]);
+   * // => true
+   *
+   * _.isArrayLikeObject(document.body.children);
+   * // => true
+   *
+   * _.isArrayLikeObject('abc');
+   * // => false
+   *
+   * _.isArrayLikeObject(_.noop);
+   * // => false
+   */
+  function isArrayLikeObject(value) {
+    return isObjectLike(value) && isArrayLike(value);
+  }
+
+  /**
+   * Checks if `value` is classified as a boolean primitive or object.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isBoolean(false);
+   * // => true
+   *
+   * _.isBoolean(null);
+   * // => false
+   */
+  function isBoolean(value) {
+    return value === true || value === false ||
+      (isObjectLike(value) && objectToString.call(value) == boolTag);
+  }
+
+  /**
+   * Checks if `value` is classified as a `Date` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isDate(new Date);
+   * // => true
+   *
+   * _.isDate('Mon April 23 2012');
+   * // => false
+   */
+  function isDate(value) {
+    return isObjectLike(value) && objectToString.call(value) == dateTag;
+  }
+
+  /**
+   * Checks if `value` is an empty object, collection, map, or set.
+   *
+   * Objects are considered empty if they have no own enumerable string keyed
+   * properties.
+   *
+   * Array-like values such as `arguments` objects, arrays, buffers, strings, or
+   * jQuery-like collections are considered empty if they have a `length` of `0`.
+   * Similarly, maps and sets are considered empty if they have a `size` of `0`.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+   * @example
+   *
+   * _.isEmpty(null);
+   * // => true
+   *
+   * _.isEmpty(true);
+   * // => true
+   *
+   * _.isEmpty(1);
+   * // => true
+   *
+   * _.isEmpty([1, 2, 3]);
+   * // => false
+   *
+   * _.isEmpty({ 'a': 1 });
+   * // => false
+   */
+  function isEmpty(value) {
+    if (isArrayLike(value) &&
+        (isArray(value) || isString(value) ||
+          isFunction(value.splice) || isArguments(value))) {
+      return !value.length;
+    }
+    return !keys(value).length;
+  }
+
+  /**
+   * Performs a deep comparison between two values to determine if they are
+   * equivalent.
+   *
+   * **Note:** This method supports comparing arrays, array buffers, booleans,
+   * date objects, error objects, maps, numbers, `Object` objects, regexes,
+   * sets, strings, symbols, and typed arrays. `Object` objects are compared
+   * by their own, not inherited, enumerable properties. Functions and DOM
+   * nodes are **not** supported.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to compare.
+   * @param {*} other The other value to compare.
+   * @returns {boolean} Returns `true` if the values are equivalent,
+   *  else `false`.
+   * @example
+   *
+   * var object = { 'user': 'fred' };
+   * var other = { 'user': 'fred' };
+   *
+   * _.isEqual(object, other);
+   * // => true
+   *
+   * object === other;
+   * // => false
+   */
+  function isEqual(value, other) {
+    return baseIsEqual(value, other);
+  }
+
+  /**
+   * Checks if `value` is a finite primitive number.
+   *
+   * **Note:** This method is based on
+   * [`Number.isFinite`](https://mdn.io/Number/isFinite).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is a finite number,
+   *  else `false`.
+   * @example
+   *
+   * _.isFinite(3);
+   * // => true
+   *
+   * _.isFinite(Number.MAX_VALUE);
+   * // => true
+   *
+   * _.isFinite(3.14);
+   * // => true
+   *
+   * _.isFinite(Infinity);
+   * // => false
+   */
+  function isFinite(value) {
+    return typeof value == 'number' && nativeIsFinite(value);
+  }
+
+  /**
+   * Checks if `value` is classified as a `Function` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isFunction(_);
+   * // => true
+   *
+   * _.isFunction(/abc/);
+   * // => false
+   */
+  function isFunction(value) {
+    // The use of `Object#toString` avoids issues with the `typeof` operator
+    // in Safari 8 which returns 'object' for typed array and weak map constructors,
+    // and PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+    var tag = isObject(value) ? objectToString.call(value) : '';
+    return tag == funcTag || tag == genTag;
+  }
+
+  /**
+   * Checks if `value` is a valid array-like length.
+   *
+   * **Note:** This function is loosely based on
+   * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is a valid length,
+   *  else `false`.
+   * @example
+   *
+   * _.isLength(3);
+   * // => true
+   *
+   * _.isLength(Number.MIN_VALUE);
+   * // => false
+   *
+   * _.isLength(Infinity);
+   * // => false
+   *
+   * _.isLength('3');
+   * // => false
+   */
+  function isLength(value) {
+    return typeof value == 'number' &&
+      value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+  }
+
+  /**
+   * Checks if `value` is the
+   * [language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types)
+   * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+   * @example
+   *
+   * _.isObject({});
+   * // => true
+   *
+   * _.isObject([1, 2, 3]);
+   * // => true
+   *
+   * _.isObject(_.noop);
+   * // => true
+   *
+   * _.isObject(null);
+   * // => false
+   */
+  function isObject(value) {
+    var type = typeof value;
+    return !!value && (type == 'object' || type == 'function');
+  }
+
+  /**
+   * Checks if `value` is object-like. A value is object-like if it's not `null`
+   * and has a `typeof` result of "object".
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+   * @example
+   *
+   * _.isObjectLike({});
+   * // => true
+   *
+   * _.isObjectLike([1, 2, 3]);
+   * // => true
+   *
+   * _.isObjectLike(_.noop);
+   * // => false
+   *
+   * _.isObjectLike(null);
+   * // => false
+   */
+  function isObjectLike(value) {
+    return !!value && typeof value == 'object';
+  }
+
+  /**
+   * Checks if `value` is `NaN`.
+   *
+   * **Note:** This method is based on
+   * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+   * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+   * `undefined` and other non-number values.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+   * @example
+   *
+   * _.isNaN(NaN);
+   * // => true
+   *
+   * _.isNaN(new Number(NaN));
+   * // => true
+   *
+   * isNaN(undefined);
+   * // => true
+   *
+   * _.isNaN(undefined);
+   * // => false
+   */
+  function isNaN(value) {
+    // An `NaN` primitive is the only value that is not equal to itself.
+    // Perform the `toStringTag` check first to avoid errors with some
+    // ActiveX objects in IE.
+    return isNumber(value) && value != +value;
+  }
+
+  /**
+   * Checks if `value` is `null`.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+   * @example
+   *
+   * _.isNull(null);
+   * // => true
+   *
+   * _.isNull(void 0);
+   * // => false
+   */
+  function isNull(value) {
+    return value === null;
+  }
+
+  /**
+   * Checks if `value` is classified as a `Number` primitive or object.
+   *
+   * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+   * classified as numbers, use the `_.isFinite` method.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isNumber(3);
+   * // => true
+   *
+   * _.isNumber(Number.MIN_VALUE);
+   * // => true
+   *
+   * _.isNumber(Infinity);
+   * // => true
+   *
+   * _.isNumber('3');
+   * // => false
+   */
+  function isNumber(value) {
+    return typeof value == 'number' ||
+      (isObjectLike(value) && objectToString.call(value) == numberTag);
+  }
+
+  /**
+   * Checks if `value` is classified as a `RegExp` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 0.1.0
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isRegExp(/abc/);
+   * // => true
+   *
+   * _.isRegExp('/abc/');
+   * // => false
+   */
+  function isRegExp(value) {
+    return isObject(value) && objectToString.call(value) == regexpTag;
+  }
+
+  /**
+   * Checks if `value` is classified as a `String` primitive or object.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is correctly classified,
+   *  else `false`.
+   * @example
+   *
+   * _.isString('abc');
+   * // => true
+   *
+   * _.isString(1);
+   * // => false
+   */
+  function isString(value) {
+    return typeof value == 'string' ||
+      (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag);
+  }
+
+  /**
+   * Checks if `value` is `undefined`.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Lang
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+   * @example
+   *
+   * _.isUndefined(void 0);
+   * // => true
+   *
+   * _.isUndefined(null);
+   * // => false
+   */
+  function isUndefined(value) {
+    return value === undefined;
+  }
+
+  /**
+   * Converts `value` to an array.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Lang
+   * @param {*} value The value to convert.
+   * @returns {Array} Returns the converted array.
+   * @example
+   *
+   * _.toArray({ 'a': 1, 'b': 2 });
+   * // => [1, 2]
+   *
+   * _.toArray('abc');
+   * // => ['a', 'b', 'c']
+   *
+   * _.toArray(1);
+   * // => []
+   *
+   * _.toArray(null);
+   * // => []
+   */
+  function toArray(value) {
+    if (!isArrayLike(value)) {
+      return values(value);
+    }
+    return value.length ? copyArray(value) : [];
+  }
+
+  /**
+   * Converts `value` to an integer.
+   *
+   * **Note:** This function is loosely based on
+   * [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to convert.
+   * @returns {number} Returns the converted integer.
+   * @example
+   *
+   * _.toInteger(3);
+   * // => 3
+   *
+   * _.toInteger(Number.MIN_VALUE);
+   * // => 0
+   *
+   * _.toInteger(Infinity);
+   * // => 1.7976931348623157e+308
+   *
+   * _.toInteger('3');
+   * // => 3
+   */
+  var toInteger = Number;
+
+  /**
+   * Converts `value` to a number.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to process.
+   * @returns {number} Returns the number.
+   * @example
+   *
+   * _.toNumber(3);
+   * // => 3
+   *
+   * _.toNumber(Number.MIN_VALUE);
+   * // => 5e-324
+   *
+   * _.toNumber(Infinity);
+   * // => Infinity
+   *
+   * _.toNumber('3');
+   * // => 3
+   */
+  var toNumber = Number;
+
+  /**
+   * Converts `value` to a string. An empty string is returned for `null`
+   * and `undefined` values. The sign of `-0` is preserved.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @category Lang
+   * @param {*} value The value to process.
+   * @returns {string} Returns the string.
+   * @example
+   *
+   * _.toString(null);
+   * // => ''
+   *
+   * _.toString(-0);
+   * // => '-0'
+   *
+   * _.toString([1, 2, 3]);
+   * // => '1,2,3'
+   */
+  function toString(value) {
+    if (typeof value == 'string') {
+      return value;
+    }
+    return value == null ? '' : (value + '');
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Assigns own enumerable string keyed properties of source objects to the
+   * destination object. Source objects are applied from left to right.
+   * Subsequent sources overwrite property assignments of previous sources.
+   *
+   * **Note:** This method mutates `object` and is loosely based on
+   * [`Object.assign`](https://mdn.io/Object/assign).
+   *
+   * @static
+   * @memberOf _
+   * @since 0.10.0
+   * @category Object
+   * @param {Object} object The destination object.
+   * @param {...Object} [sources] The source objects.
+   * @returns {Object} Returns `object`.
+   * @see _.assignIn
+   * @example
+   *
+   * function Foo() {
+   *   this.c = 3;
+   * }
+   *
+   * function Bar() {
+   *   this.e = 5;
+   * }
+   *
+   * Foo.prototype.d = 4;
+   * Bar.prototype.f = 6;
+   *
+   * _.assign({ 'a': 1 }, new Foo, new Bar);
+   * // => { 'a': 1, 'c': 3, 'e': 5 }
+   */
+  var assign = createAssigner(function(object, source) {
+    copyObject(source, keys(source), object);
+  });
+
+  /**
+   * This method is like `_.assign` except that it iterates over own and
+   * inherited source properties.
+   *
+   * **Note:** This method mutates `object`.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @alias extend
+   * @category Object
+   * @param {Object} object The destination object.
+   * @param {...Object} [sources] The source objects.
+   * @returns {Object} Returns `object`.
+   * @see _.assign
+   * @example
+   *
+   * function Foo() {
+   *   this.b = 2;
+   * }
+   *
+   * function Bar() {
+   *   this.d = 4;
+   * }
+   *
+   * Foo.prototype.c = 3;
+   * Bar.prototype.e = 5;
+   *
+   * _.assignIn({ 'a': 1 }, new Foo, new Bar);
+   * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }
+   */
+  var assignIn = createAssigner(function(object, source) {
+    copyObject(source, keysIn(source), object);
+  });
+
+  /**
+   * This method is like `_.assignIn` except that it accepts `customizer`
+   * which is invoked to produce the assigned values. If `customizer` returns
+   * `undefined`, assignment is handled by the method instead. The `customizer`
+   * is invoked with five arguments: (objValue, srcValue, key, object, source).
+   *
+   * **Note:** This method mutates `object`.
+   *
+   * @static
+   * @memberOf _
+   * @since 4.0.0
+   * @alias extendWith
+   * @category Object
+   * @param {Object} object The destination object.
+   * @param {...Object} sources The source objects.
+   * @param {Function} [customizer] The function to customize assigned values.
+   * @returns {Object} Returns `object`.
+   * @see _.assignWith
+   * @example
+   *
+   * function customizer(objValue, srcValue) {
+   *   return _.isUndefined(objValue) ? srcValue : objValue;
+   * }
+   *
+   * var defaults = _.partialRight(_.assignInWith, customizer);
+   *
+   * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+   * // => { 'a': 1, 'b': 2 }
+   */
+  var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
+    copyObject(source, keysIn(source), object, customizer);
+  });
+
+  /**
+   * Creates an object that inherits from the `prototype` object. If a
+   * `properties` object is given, its own enumerable string keyed properties
+   * are assigned to the created object.
+   *
+   * @static
+   * @memberOf _
+   * @since 2.3.0
+   * @category Object
+   * @param {Object} prototype The object to inherit from.
+   * @param {Object} [properties] The properties to assign to the object.
+   * @returns {Object} Returns the new object.
+   * @example
+   *
+   * function Shape() {
+   *   this.x = 0;
+   *   this.y = 0;
+   * }
+   *
+   * function Circle() {
+   *   Shape.call(this);
+   * }
+   *
+   * Circle.prototype = _.create(Shape.prototype, {
+   *   'constructor': Circle
+   * });
+   *
+   * var circle = new Circle;
+   * circle instanceof Circle;
+   * // => true
+   *
+   * circle instanceof Shape;
+   * // => true
+   */
+  function create(prototype, properties) {
+    var result = baseCreate(prototype);
+    return properties ? assign(result, properties) : result;
+  }
+
+  /**
+   * Assigns own and inherited enumerable string keyed properties of source
+   * objects to the destination object for all destination properties that
+   * resolve to `undefined`. Source objects are applied from left to right.
+   * Once a property is set, additional values of the same property are ignored.
+   *
+   * **Note:** This method mutates `object`.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Object
+   * @param {Object} object The destination object.
+   * @param {...Object} [sources] The source objects.
+   * @returns {Object} Returns `object`.
+   * @see _.defaultsDeep
+   * @example
+   *
+   * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+   * // => { 'user': 'barney', 'age': 36 }
+   */
+  var defaults = rest(function(args) {
+    args.push(undefined, assignInDefaults);
+    return assignInWith.apply(undefined, args);
+  });
+
+  /**
+   * Checks if `path` is a direct property of `object`.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Object
+   * @param {Object} object The object to query.
+   * @param {Array|string} path The path to check.
+   * @returns {boolean} Returns `true` if `path` exists, else `false`.
+   * @example
+   *
+   * var object = { 'a': { 'b': 2 } };
+   * var other = _.create({ 'a': _.create({ 'b': 2 }) });
+   *
+   * _.has(object, 'a');
+   * // => true
+   *
+   * _.has(object, 'a.b');
+   * // => true
+   *
+   * _.has(object, ['a', 'b']);
+   * // => true
+   *
+   * _.has(other, 'a');
+   * // => false
+   */
+  function has(object, path) {
+    return object != null && hasOwnProperty.call(object, path);
+  }
+
+  /**
+   * Creates an array of the own enumerable property names of `object`.
+   *
+   * **Note:** Non-object values are coerced to objects. See the
+   * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+   * for more details.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Object
+   * @param {Object} object The object to query.
+   * @returns {Array} Returns the array of property names.
+   * @example
+   *
+   * function Foo() {
+   *   this.a = 1;
+   *   this.b = 2;
+   * }
+   *
+   * Foo.prototype.c = 3;
+   *
+   * _.keys(new Foo);
+   * // => ['a', 'b'] (iteration order is not guaranteed)
+   *
+   * _.keys('hi');
+   * // => ['0', '1']
+   */
+  function keys(object) {
+    var isProto = isPrototype(object);
+    if (!(isProto || isArrayLike(object))) {
+      return baseKeys(object);
+    }
+    var indexes = indexKeys(object),
+        skipIndexes = !!indexes,
+        result = indexes || [],
+        length = result.length;
+
+    for (var key in object) {
+      if (hasOwnProperty.call(object, key) &&
+          !(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+          !(isProto && key == 'constructor')) {
+        result.push(key);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates an array of the own and inherited enumerable property names of `object`.
+   *
+   * **Note:** Non-object values are coerced to objects.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Object
+   * @param {Object} object The object to query.
+   * @returns {Array} Returns the array of property names.
+   * @example
+   *
+   * function Foo() {
+   *   this.a = 1;
+   *   this.b = 2;
+   * }
+   *
+   * Foo.prototype.c = 3;
+   *
+   * _.keysIn(new Foo);
+   * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+   */
+  function keysIn(object) {
+    var index = -1,
+        isProto = isPrototype(object),
+        props = baseKeysIn(object),
+        propsLength = props.length,
+        indexes = indexKeys(object),
+        skipIndexes = !!indexes,
+        result = indexes || [],
+        length = result.length;
+
+    while (++index < propsLength) {
+      var key = props[index];
+      if (!(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+          !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+        result.push(key);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates an object composed of the picked `object` properties.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Object
+   * @param {Object} object The source object.
+   * @param {...(string|string[])} [props] The property identifiers to pick.
+   * @returns {Object} Returns the new object.
+   * @example
+   *
+   * var object = { 'a': 1, 'b': '2', 'c': 3 };
+   *
+   * _.pick(object, ['a', 'c']);
+   * // => { 'a': 1, 'c': 3 }
+   */
+  var pick = rest(function(object, props) {
+    return object == null ? {} : basePick(object, baseMap(baseFlatten(props, 1), toKey));
+  });
+
+  /**
+   * This method is like `_.get` except that if the resolved value is a
+   * function it's invoked with the `this` binding of its parent object and
+   * its result is returned.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Object
+   * @param {Object} object The object to query.
+   * @param {Array|string} path The path of the property to resolve.
+   * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+   * @returns {*} Returns the resolved value.
+   * @example
+   *
+   * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+   *
+   * _.result(object, 'a[0].b.c1');
+   * // => 3
+   *
+   * _.result(object, 'a[0].b.c2');
+   * // => 4
+   *
+   * _.result(object, 'a[0].b.c3', 'default');
+   * // => 'default'
+   *
+   * _.result(object, 'a[0].b.c3', _.constant('default'));
+   * // => 'default'
+   */
+  function result(object, path, defaultValue) {
+    var value = object == null ? undefined : object[path];
+    if (value === undefined) {
+      value = defaultValue;
+    }
+    return isFunction(value) ? value.call(object) : value;
+  }
+
+  /**
+   * Creates an array of the own enumerable string keyed property values of `object`.
+   *
+   * **Note:** Non-object values are coerced to objects.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Object
+   * @param {Object} object The object to query.
+   * @returns {Array} Returns the array of property values.
+   * @example
+   *
+   * function Foo() {
+   *   this.a = 1;
+   *   this.b = 2;
+   * }
+   *
+   * Foo.prototype.c = 3;
+   *
+   * _.values(new Foo);
+   * // => [1, 2] (iteration order is not guaranteed)
+   *
+   * _.values('hi');
+   * // => ['h', 'i']
+   */
+  function values(object) {
+    return object ? baseValues(object, keys(object)) : [];
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to
+   * their corresponding HTML entities.
+   *
+   * **Note:** No other characters are escaped. To escape additional
+   * characters use a third-party library like [_he_](https://mths.be/he).
+   *
+   * Though the ">" character is escaped for symmetry, characters like
+   * ">" and "/" don't need escaping in HTML and have no special meaning
+   * unless they're part of a tag or unquoted attribute value. See
+   * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+   * (under "semi-related fun fact") for more details.
+   *
+   * Backticks are escaped because in IE < 9, they can break out of
+   * attribute values or HTML comments. See [#59](https://html5sec.org/#59),
+   * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
+   * [#133](https://html5sec.org/#133) of the
+   * [HTML5 Security Cheatsheet](https://html5sec.org/) for more details.
+   *
+   * When working with HTML you should always
+   * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
+   * XSS vectors.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category String
+   * @param {string} [string=''] The string to escape.
+   * @returns {string} Returns the escaped string.
+   * @example
+   *
+   * _.escape('fred, barney, & pebbles');
+   * // => 'fred, barney, &amp; pebbles'
+   */
+  function escape(string) {
+    string = toString(string);
+    return (string && reHasUnescapedHtml.test(string))
+      ? string.replace(reUnescapedHtml, escapeHtmlChar)
+      : string;
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * This method returns the first argument given to it.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Util
+   * @param {*} value Any value.
+   * @returns {*} Returns `value`.
+   * @example
+   *
+   * var object = { 'user': 'fred' };
+   *
+   * _.identity(object) === object;
+   * // => true
+   */
+  function identity(value) {
+    return value;
+  }
+
+  /**
+   * Creates a function that invokes `func` with the arguments of the created
+   * function. If `func` is a property name, the created function returns the
+   * property value for a given element. If `func` is an array or object, the
+   * created function returns `true` for elements that contain the equivalent
+   * source properties, otherwise it returns `false`.
+   *
+   * @static
+   * @since 4.0.0
+   * @memberOf _
+   * @category Util
+   * @param {*} [func=_.identity] The value to convert to a callback.
+   * @returns {Function} Returns the callback.
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'barney', 'age': 36, 'active': true },
+   *   { 'user': 'fred',   'age': 40, 'active': false }
+   * ];
+   *
+   * // The `_.matches` iteratee shorthand.
+   * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+   * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
+   *
+   * // The `_.matchesProperty` iteratee shorthand.
+   * _.filter(users, _.iteratee(['user', 'fred']));
+   * // => [{ 'user': 'fred', 'age': 40 }]
+   *
+   * // The `_.property` iteratee shorthand.
+   * _.map(users, _.iteratee('user'));
+   * // => ['barney', 'fred']
+   *
+   * // Create custom iteratee shorthands.
+   * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+   *   return !_.isRegExp(func) ? iteratee(func) : function(string) {
+   *     return func.test(string);
+   *   };
+   * });
+   *
+   * _.filter(['abc', 'def'], /ef/);
+   * // => ['def']
+   */
+  var iteratee = baseIteratee;
+
+  /**
+   * Creates a function that performs a partial deep comparison between a given
+   * object and `source`, returning `true` if the given object has equivalent
+   * property values, else `false`. The created function is equivalent to
+   * `_.isMatch` with a `source` partially applied.
+   *
+   * **Note:** This method supports comparing the same values as `_.isEqual`.
+   *
+   * @static
+   * @memberOf _
+   * @since 3.0.0
+   * @category Util
+   * @param {Object} source The object of property values to match.
+   * @returns {Function} Returns the new function.
+   * @example
+   *
+   * var users = [
+   *   { 'user': 'barney', 'age': 36, 'active': true },
+   *   { 'user': 'fred',   'age': 40, 'active': false }
+   * ];
+   *
+   * _.filter(users, _.matches({ 'age': 40, 'active': false }));
+   * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
+   */
+  function matches(source) {
+    return baseMatches(assign({}, source));
+  }
+
+  /**
+   * Adds all own enumerable string keyed function properties of a source
+   * object to the destination object. If `object` is a function, then methods
+   * are added to its prototype as well.
+   *
+   * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+   * avoid conflicts caused by modifying the original.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Util
+   * @param {Function|Object} [object=lodash] The destination object.
+   * @param {Object} source The object of functions to add.
+   * @param {Object} [options={}] The options object.
+   * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
+   * @returns {Function|Object} Returns `object`.
+   * @example
+   *
+   * function vowels(string) {
+   *   return _.filter(string, function(v) {
+   *     return /[aeiou]/i.test(v);
+   *   });
+   * }
+   *
+   * _.mixin({ 'vowels': vowels });
+   * _.vowels('fred');
+   * // => ['e']
+   *
+   * _('fred').vowels().value();
+   * // => ['e']
+   *
+   * _.mixin({ 'vowels': vowels }, { 'chain': false });
+   * _('fred').vowels();
+   * // => ['e']
+   */
+  function mixin(object, source, options) {
+    var props = keys(source),
+        methodNames = baseFunctions(source, props);
+
+    if (options == null &&
+        !(isObject(source) && (methodNames.length || !props.length))) {
+      options = source;
+      source = object;
+      object = this;
+      methodNames = baseFunctions(source, keys(source));
+    }
+    var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
+        isFunc = isFunction(object);
+
+    baseEach(methodNames, function(methodName) {
+      var func = source[methodName];
+      object[methodName] = func;
+      if (isFunc) {
+        object.prototype[methodName] = function() {
+          var chainAll = this.__chain__;
+          if (chain || chainAll) {
+            var result = object(this.__wrapped__),
+                actions = result.__actions__ = copyArray(this.__actions__);
+
+            actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+            result.__chain__ = chainAll;
+            return result;
+          }
+          return func.apply(object, arrayPush([this.value()], arguments));
+        };
+      }
+    });
+
+    return object;
+  }
+
+  /**
+   * Reverts the `_` variable to its previous value and returns a reference to
+   * the `lodash` function.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Util
+   * @returns {Function} Returns the `lodash` function.
+   * @example
+   *
+   * var lodash = _.noConflict();
+   */
+  function noConflict() {
+    if (root._ === this) {
+      root._ = oldDash;
+    }
+    return this;
+  }
+
+  /**
+   * A no-operation function that returns `undefined` regardless of the
+   * arguments it receives.
+   *
+   * @static
+   * @memberOf _
+   * @since 2.3.0
+   * @category Util
+   * @example
+   *
+   * var object = { 'user': 'fred' };
+   *
+   * _.noop(object) === undefined;
+   * // => true
+   */
+  function noop() {
+    // No operation performed.
+  }
+
+  /**
+   * Generates a unique ID. If `prefix` is given, the ID is appended to it.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Util
+   * @param {string} [prefix=''] The value to prefix the ID with.
+   * @returns {string} Returns the unique ID.
+   * @example
+   *
+   * _.uniqueId('contact_');
+   * // => 'contact_104'
+   *
+   * _.uniqueId();
+   * // => '105'
+   */
+  function uniqueId(prefix) {
+    var id = ++idCounter;
+    return toString(prefix) + id;
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * Computes the maximum value of `array`. If `array` is empty or falsey,
+   * `undefined` is returned.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Math
+   * @param {Array} array The array to iterate over.
+   * @returns {*} Returns the maximum value.
+   * @example
+   *
+   * _.max([4, 2, 8, 6]);
+   * // => 8
+   *
+   * _.max([]);
+   * // => undefined
+   */
+  function max(array) {
+    return (array && array.length)
+      ? baseExtremum(array, identity, baseGt)
+      : undefined;
+  }
+
+  /**
+   * Computes the minimum value of `array`. If `array` is empty or falsey,
+   * `undefined` is returned.
+   *
+   * @static
+   * @since 0.1.0
+   * @memberOf _
+   * @category Math
+   * @param {Array} array The array to iterate over.
+   * @returns {*} Returns the minimum value.
+   * @example
+   *
+   * _.min([4, 2, 8, 6]);
+   * // => 2
+   *
+   * _.min([]);
+   * // => undefined
+   */
+  function min(array) {
+    return (array && array.length)
+      ? baseExtremum(array, identity, baseLt)
+      : undefined;
+  }
+
+  /*------------------------------------------------------------------------*/
+
+  // Add methods that return wrapped values in chain sequences.
+  lodash.assignIn = assignIn;
+  lodash.before = before;
+  lodash.bind = bind;
+  lodash.chain = chain;
+  lodash.compact = compact;
+  lodash.concat = concat;
+  lodash.create = create;
+  lodash.defaults = defaults;
+  lodash.defer = defer;
+  lodash.delay = delay;
+  lodash.filter = filter;
+  lodash.flatten = flatten;
+  lodash.flattenDeep = flattenDeep;
+  lodash.iteratee = iteratee;
+  lodash.keys = keys;
+  lodash.map = map;
+  lodash.matches = matches;
+  lodash.mixin = mixin;
+  lodash.negate = negate;
+  lodash.once = once;
+  lodash.pick = pick;
+  lodash.slice = slice;
+  lodash.sortBy = sortBy;
+  lodash.tap = tap;
+  lodash.thru = thru;
+  lodash.toArray = toArray;
+  lodash.values = values;
+
+  // Add aliases.
+  lodash.extend = assignIn;
+
+  // Add methods to `lodash.prototype`.
+  mixin(lodash, lodash);
+
+  /*------------------------------------------------------------------------*/
+
+  // Add methods that return unwrapped values in chain sequences.
+  lodash.clone = clone;
+  lodash.escape = escape;
+  lodash.every = every;
+  lodash.find = find;
+  lodash.forEach = forEach;
+  lodash.has = has;
+  lodash.head = head;
+  lodash.identity = identity;
+  lodash.indexOf = indexOf;
+  lodash.isArguments = isArguments;
+  lodash.isArray = isArray;
+  lodash.isBoolean = isBoolean;
+  lodash.isDate = isDate;
+  lodash.isEmpty = isEmpty;
+  lodash.isEqual = isEqual;
+  lodash.isFinite = isFinite;
+  lodash.isFunction = isFunction;
+  lodash.isNaN = isNaN;
+  lodash.isNull = isNull;
+  lodash.isNumber = isNumber;
+  lodash.isObject = isObject;
+  lodash.isRegExp = isRegExp;
+  lodash.isString = isString;
+  lodash.isUndefined = isUndefined;
+  lodash.last = last;
+  lodash.max = max;
+  lodash.min = min;
+  lodash.noConflict = noConflict;
+  lodash.noop = noop;
+  lodash.reduce = reduce;
+  lodash.result = result;
+  lodash.size = size;
+  lodash.some = some;
+  lodash.uniqueId = uniqueId;
+
+  // Add aliases.
+  lodash.each = forEach;
+  lodash.first = head;
+
+  mixin(lodash, (function() {
+    var source = {};
+    baseForOwn(lodash, function(func, methodName) {
+      if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+        source[methodName] = func;
+      }
+    });
+    return source;
+  }()), { 'chain': false });
+
+  /*------------------------------------------------------------------------*/
+
+  /**
+   * The semantic version number.
+   *
+   * @static
+   * @memberOf _
+   * @type {string}
+   */
+  lodash.VERSION = VERSION;
+
+  // Add `Array` methods to `lodash.prototype`.
+  baseEach(['pop', 'join', 'replace', 'reverse', 'split', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+    var func = (/^(?:replace|split)$/.test(methodName) ? String.prototype : arrayProto)[methodName],
+        chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+        retUnwrapped = /^(?:pop|join|replace|shift)$/.test(methodName);
+
+    lodash.prototype[methodName] = function() {
+      var args = arguments;
+      if (retUnwrapped && !this.__chain__) {
+        var value = this.value();
+        return func.apply(isArray(value) ? value : [], args);
+      }
+      return this[chainName](function(value) {
+        return func.apply(isArray(value) ? value : [], args);
+      });
+    };
+  });
+
+  // Add chain sequence methods to the `lodash` wrapper.
+  lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+  /*--------------------------------------------------------------------------*/
+
+  // Expose Lodash on the free variable `window` or `self` when available so it's
+  // globally accessible, even when bundled with Browserify, Webpack, etc. This
+  // also prevents errors in cases where Lodash is loaded by a script tag in the
+  // presence of an AMD loader. See http://requirejs.org/docs/errors.html#mismatch
+  // for more details. Use `_.noConflict` to remove Lodash from the global object.
+  (freeWindow || freeSelf || {})._ = lodash;
+
+  // Some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module.
+    define(function() {
+      return lodash;
+    });
+  }
+  // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
+  else if (freeExports && freeModule) {
+    // Export for Node.js.
+    if (moduleExports) {
+      (freeModule.exports = lodash)._ = lodash;
+    }
+    // Export for CommonJS support.
+    freeExports._ = lodash;
+  }
+  else {
+    // Export to the global object.
+    root._ = lodash;
+  }
+}.call(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.core.min.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.core.min.js
new file mode 100644
index 0000000..af9a225
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.core.min.js
@@ -0,0 +1,30 @@
+/**
+ * @license
+ * lodash 4.11.2 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
+ * Build: `lodash core -o ./dist/lodash.core.js`
+ */
+;(function(){function n(n,t){return n.push.apply(n,t),n}function t(n,t,r){var e;return r(n,function(n,r,u){return t(n,r,u)?(e=n,false):void 0}),e}function r(n,t,r,e,u){return u(n,function(n,u,o){r=e?(e=false,n):t(r,n,u,o)}),r}function e(n,t){return O(t,function(t){return n[t]})}function u(n){return n&&n.Object===Object?n:null}function o(n){return gn[n]}function i(n){var t=false;if(null!=n&&typeof n.toString!="function")try{t=!!(n+"")}catch(r){}return t}function c(n){return n instanceof f?n:new f(n)}function f(n,t){
+this.__wrapped__=n,this.__actions__=[],this.__chain__=!!t}function a(n,t,r,e){var u;return(u=n===pn)||(u=En[r],u=(n===u||n!==n&&u!==u)&&!kn.call(e,r)),u?t:n}function l(n){return nn(n)?Bn(n):{}}function p(n,t,r){if(typeof n!="function")throw new TypeError("Expected a function");return setTimeout(function(){n.apply(pn,r)},t)}function s(n,t){var r=true;return zn(n,function(n,e,u){return r=!!t(n,e,u)}),r}function h(n,t,r){for(var e=-1,u=n.length;++e<u;){var o=n[e],i=t(o);if(null!=i&&(c===pn?i===i:r(i,c)))var c=i,f=o;
+}return f}function v(n,t){var r=[];return zn(n,function(n,e,u){t(n,e,u)&&r.push(n)}),r}function y(t,r,e,u,o){var i=-1,c=t.length;for(e||(e=G),o||(o=[]);++i<c;){var f=t[i];r>0&&e(f)?r>1?y(f,r-1,e,u,o):n(o,f):u||(o[o.length]=f)}return o}function g(n,t){return n&&Cn(n,t,on)}function b(n,t){return v(t,function(t){return Y(n[t])})}function _(n,t){return n>t}function d(n,t,r,e,u){return n===t?true:null==n||null==t||!nn(n)&&!tn(t)?n!==n&&t!==t:j(n,t,d,r,e,u)}function j(n,t,r,e,u,o){var c=Vn(n),f=Vn(t),a="[object Array]",l="[object Array]";
+c||(a=Sn.call(n),a="[object Arguments]"==a?"[object Object]":a),f||(l=Sn.call(t),l="[object Arguments]"==l?"[object Object]":l);var p="[object Object]"==a&&!i(n),f="[object Object]"==l&&!i(t),l=a==l;o||(o=[]);var s=U(o,function(t){return t[0]===n});return s&&s[1]?s[1]==t:(o.push([n,t]),l&&!p?(r=c||isTypedArray(n)?$(n,t,r,e,u,o):q(n,t,a),o.pop(),r):2&u||(c=p&&kn.call(n,"__wrapped__"),a=f&&kn.call(t,"__wrapped__"),!c&&!a)?l?(r=z(n,t,r,e,u,o),o.pop(),r):false:(c=c?n.value():n,t=a?t.value():t,r=r(c,t,e,u,o),
+o.pop(),r))}function m(n){return typeof n=="function"?n:null==n?an:(typeof n=="object"?A:k)(n)}function w(n){n=null==n?n:Object(n);var t,r=[];for(t in n)r.push(t);return r}function x(n,t){return t>n}function O(n,t){var r=-1,e=X(n)?Array(n.length):[];return zn(n,function(n,u,o){e[++r]=t(n,u,o)}),e}function A(n){var t=on(n);return function(r){var e=t.length;if(null==r)return!e;for(r=Object(r);e--;){var u=t[e];if(!(u in r&&d(n[u],r[u],pn,3)))return false}return true}}function E(n,t){return n=Object(n),H(t,function(t,r){
+return r in n&&(t[r]=n[r]),t},{})}function k(n){return function(t){return null==t?pn:t[n]}}function N(n,t,r){var e=-1,u=n.length;for(0>t&&(t=-t>u?0:u+t),r=r>u?u:r,0>r&&(r+=u),u=t>r?0:r-t>>>0,t>>>=0,r=Array(u);++e<u;)r[e]=n[e+t];return r}function S(n){return N(n,0,n.length)}function T(n,t){var r;return zn(n,function(n,e,u){return r=t(n,e,u),!r}),!!r}function F(t,r){return H(r,function(t,r){return r.func.apply(r.thisArg,n([t],r.args))},t)}function R(n,t,r,e){r||(r={});for(var u=-1,o=t.length;++u<o;){
+var i=t[u],c=e?e(r[i],n[i],i,r,n):n[i],f=r,a=f[i];kn.call(f,i)&&(a===c||a!==a&&c!==c)&&(c!==pn||i in f)||(f[i]=c)}return r}function B(n){return L(function(t,r){var e=-1,u=r.length,o=u>1?r[u-1]:pn,o=typeof o=="function"?(u--,o):pn;for(t=Object(t);++e<u;){var i=r[e];i&&n(t,i,e,o)}return t})}function D(n){return function(){var t=arguments,r=l(n.prototype),t=n.apply(r,t);return nn(t)?t:r}}function I(n,t,r){function e(){for(var o=-1,i=arguments.length,c=-1,f=r.length,a=Array(f+i),l=this&&this!==On&&this instanceof e?u:n;++c<f;)a[c]=r[c];
+for(;i--;)a[c++]=arguments[++o];return l.apply(t,a)}if(typeof n!="function")throw new TypeError("Expected a function");var u=D(n);return e}function $(n,t,r,e,u,o){var i=-1,c=1&u,f=n.length,a=t.length;if(f!=a&&!(2&u&&a>f))return false;for(a=true;++i<f;){var l=n[i],p=t[i];if(void 0!==pn){a=false;break}if(c){if(!T(t,function(n){return l===n||r(l,n,e,u,o)})){a=false;break}}else if(l!==p&&!r(l,p,e,u,o)){a=false;break}}return a}function q(n,t,r){switch(r){case"[object Boolean]":case"[object Date]":return+n==+t;case"[object Error]":
+return n.name==t.name&&n.message==t.message;case"[object Number]":return n!=+n?t!=+t:n==+t;case"[object RegExp]":case"[object String]":return n==t+""}return false}function z(n,t,r,e,u,o){var i=2&u,c=on(n),f=c.length,a=on(t).length;if(f!=a&&!i)return false;for(var l=f;l--;){var p=c[l];if(!(i?p in t:kn.call(t,p)))return false}for(a=true;++l<f;){var p=c[l],s=n[p],h=t[p];if(void 0!==pn||s!==h&&!r(s,h,e,u,o)){a=false;break}i||(i="constructor"==p)}return a&&!i&&(r=n.constructor,e=t.constructor,r!=e&&"constructor"in n&&"constructor"in t&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(a=false)),
+a}function C(n){var t=n?n.length:pn;if(Z(t)&&(Vn(n)||en(n)||W(n))){n=String;for(var r=-1,e=Array(t);++r<t;)e[r]=n(r);t=e}else t=null;return t}function G(n){return tn(n)&&X(n)&&(Vn(n)||W(n))}function J(n,t){return t=null==t?9007199254740991:t,!!t&&(typeof n=="number"||yn.test(n))&&n>-1&&0==n%1&&t>n}function M(n){var t=n&&n.constructor;return n===(typeof t=="function"&&t.prototype||En)}function P(n){return n&&n.length?n[0]:pn}function U(n,r){return t(n,m(r),zn)}function V(n,t){return zn(n,m(t))}function H(n,t,e){
+return r(n,m(t),e,3>arguments.length,zn)}function K(n,t){var r;if(typeof t!="function")throw new TypeError("Expected a function");return n=Hn(n),function(){return 0<--n&&(r=t.apply(this,arguments)),1>=n&&(t=pn),r}}function L(n){var t;if(typeof n!="function")throw new TypeError("Expected a function");return t=qn(t===pn?n.length-1:Hn(t),0),function(){for(var r=arguments,e=-1,u=qn(r.length-t,0),o=Array(u);++e<u;)o[e]=r[t+e];for(u=Array(t+1),e=-1;++e<t;)u[e]=r[e];return u[t]=o,n.apply(this,u)}}function Q(){
+if(!arguments.length)return[];var n=arguments[0];return Vn(n)?n:[n]}function W(n){return tn(n)&&X(n)&&kn.call(n,"callee")&&(!Dn.call(n,"callee")||"[object Arguments]"==Sn.call(n))}function X(n){return null!=n&&Z(Gn(n))&&!Y(n)}function Y(n){return n=nn(n)?Sn.call(n):"","[object Function]"==n||"[object GeneratorFunction]"==n}function Z(n){return typeof n=="number"&&n>-1&&0==n%1&&9007199254740991>=n}function nn(n){var t=typeof n;return!!n&&("object"==t||"function"==t)}function tn(n){return!!n&&typeof n=="object";
+}function rn(n){return typeof n=="number"||tn(n)&&"[object Number]"==Sn.call(n)}function en(n){return typeof n=="string"||!Vn(n)&&tn(n)&&"[object String]"==Sn.call(n)}function un(n){return typeof n=="string"?n:null==n?"":n+""}function on(n){var t=M(n);if(!t&&!X(n))return $n(Object(n));var r,e=C(n),u=!!e,e=e||[],o=e.length;for(r in n)!kn.call(n,r)||u&&("length"==r||J(r,o))||t&&"constructor"==r||e.push(r);return e}function cn(n){for(var t=-1,r=M(n),e=w(n),u=e.length,o=C(n),i=!!o,o=o||[],c=o.length;++t<u;){
+var f=e[t];i&&("length"==f||J(f,c))||"constructor"==f&&(r||!kn.call(n,f))||o.push(f)}return o}function fn(n){return n?e(n,on(n)):[]}function an(n){return n}function ln(t,r,e){var u=on(r),o=b(r,u);null!=e||nn(r)&&(o.length||!u.length)||(e=r,r=t,t=this,o=b(r,on(r)));var i=!(nn(e)&&"chain"in e&&!e.chain),c=Y(t);return zn(o,function(e){var u=r[e];t[e]=u,c&&(t.prototype[e]=function(){var r=this.__chain__;if(i||r){var e=t(this.__wrapped__);return(e.__actions__=S(this.__actions__)).push({func:u,args:arguments,
+thisArg:t}),e.__chain__=r,e}return u.apply(t,n([this.value()],arguments))})}),t}var pn,sn=1/0,hn=/[&<>"'`]/g,vn=RegExp(hn.source),yn=/^(?:0|[1-9]\d*)$/,gn={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},bn={"function":true,object:true},_n=bn[typeof exports]&&exports&&!exports.nodeType?exports:pn,dn=bn[typeof module]&&module&&!module.nodeType?module:pn,jn=dn&&dn.exports===_n?_n:pn,mn=u(bn[typeof self]&&self),wn=u(bn[typeof window]&&window),xn=u(bn[typeof this]&&this),On=u(_n&&dn&&typeof global=="object"&&global)||wn!==(xn&&xn.window)&&wn||mn||xn||Function("return this")(),An=Array.prototype,En=Object.prototype,kn=En.hasOwnProperty,Nn=0,Sn=En.toString,Tn=On._,Fn=On.Reflect,Rn=Fn?Fn.a:pn,Bn=Object.create,Dn=En.propertyIsEnumerable,In=On.isFinite,$n=Object.keys,qn=Math.max;
+f.prototype=l(c.prototype),f.prototype.constructor=f;var zn=function(n,t){return function(r,e){if(null==r)return r;if(!X(r))return n(r,e);for(var u=r.length,o=t?u:-1,i=Object(r);(t?o--:++o<u)&&false!==e(i[o],o,i););return r}}(g),Cn=function(n){return function(t,r,e){var u=-1,o=Object(t);e=e(t);for(var i=e.length;i--;){var c=e[n?i:++u];if(false===r(o[c],c,o))break}return t}}();Rn&&!Dn.call({valueOf:1},"valueOf")&&(w=function(n){n=Rn(n);for(var t,r=[];!(t=n.next()).done;)r.push(t.value);return r});var Gn=k("length"),Jn=String,Mn=L(function(n,t,r){
+return I(n,t,r)}),Pn=L(function(n,t){return p(n,1,t)}),Un=L(function(n,t,r){return p(n,Kn(t)||0,r)}),Vn=Array.isArray,Hn=Number,Kn=Number,Ln=B(function(n,t){R(t,on(t),n)}),Qn=B(function(n,t){R(t,cn(t),n)}),Wn=B(function(n,t,r,e){R(t,cn(t),n,e)}),Xn=L(function(n){return n.push(pn,a),Wn.apply(pn,n)}),Yn=L(function(n,t){return null==n?{}:E(n,O(y(t,1),Jn))}),Zn=m;c.assignIn=Qn,c.before=K,c.bind=Mn,c.chain=function(n){return n=c(n),n.__chain__=true,n},c.compact=function(n){return v(n,Boolean)},c.concat=function(){
+var t=arguments.length,r=Q(arguments[0]);if(2>t)return t?S(r):[];for(var e=Array(t-1);t--;)e[t-1]=arguments[t];return y(e,1),n(S(r),fn)},c.create=function(n,t){var r=l(n);return t?Ln(r,t):r},c.defaults=Xn,c.defer=Pn,c.delay=Un,c.filter=function(n,t){return v(n,m(t))},c.flatten=function(n){return n&&n.length?y(n,1):[]},c.flattenDeep=function(n){return n&&n.length?y(n,sn):[]},c.iteratee=Zn,c.keys=on,c.map=function(n,t){return O(n,m(t))},c.matches=function(n){return A(Ln({},n))},c.mixin=ln,c.negate=function(n){
+if(typeof n!="function")throw new TypeError("Expected a function");return function(){return!n.apply(this,arguments)}},c.once=function(n){return K(2,n)},c.pick=Yn,c.slice=function(n,t,r){var e=n?n.length:0;return r=r===pn?e:+r,e?N(n,null==t?0:+t,r):[]},c.sortBy=function(n,t){var r=0;return t=m(t),O(O(n,function(n,e,u){return{value:n,index:r++,criteria:t(n,e,u)}}).sort(function(n,t){var r;n:{r=n.criteria;var e=t.criteria;if(r!==e){var u=r!==pn,o=null===r,i=r===r,c=e!==pn,f=null===e,a=e===e;if(!f&&r>e||o&&c&&a||!u&&a||!i){
+r=1;break n}if(!o&&e>r||f&&u&&i||!c&&i||!a){r=-1;break n}}r=0}return r||n.index-t.index}),k("value"))},c.tap=function(n,t){return t(n),n},c.thru=function(n,t){return t(n)},c.toArray=function(n){return X(n)?n.length?S(n):[]:fn(n)},c.values=fn,c.extend=Qn,ln(c,c),c.clone=function(n){return nn(n)?Vn(n)?S(n):R(n,on(n)):n},c.escape=function(n){return(n=un(n))&&vn.test(n)?n.replace(hn,o):n},c.every=function(n,t,r){return t=r?pn:t,s(n,m(t))},c.find=U,c.forEach=V,c.has=function(n,t){return null!=n&&kn.call(n,t);
+},c.head=P,c.identity=an,c.indexOf=function(n,t,r){var e=n?n.length:0;r=typeof r=="number"?0>r?qn(e+r,0):r:0,r=(r||0)-1;for(var u=t===t;++r<e;){var o=n[r];if(u?o===t:o!==o)return r}return-1},c.isArguments=W,c.isArray=Vn,c.isBoolean=function(n){return true===n||false===n||tn(n)&&"[object Boolean]"==Sn.call(n)},c.isDate=function(n){return tn(n)&&"[object Date]"==Sn.call(n)},c.isEmpty=function(n){return X(n)&&(Vn(n)||en(n)||Y(n.splice)||W(n))?!n.length:!on(n).length},c.isEqual=function(n,t){return d(n,t)},
+c.isFinite=function(n){return typeof n=="number"&&In(n)},c.isFunction=Y,c.isNaN=function(n){return rn(n)&&n!=+n},c.isNull=function(n){return null===n},c.isNumber=rn,c.isObject=nn,c.isRegExp=function(n){return nn(n)&&"[object RegExp]"==Sn.call(n)},c.isString=en,c.isUndefined=function(n){return n===pn},c.last=function(n){var t=n?n.length:0;return t?n[t-1]:pn},c.max=function(n){return n&&n.length?h(n,an,_):pn},c.min=function(n){return n&&n.length?h(n,an,x):pn},c.noConflict=function(){return On._===this&&(On._=Tn),
+this},c.noop=function(){},c.reduce=H,c.result=function(n,t,r){return t=null==n?pn:n[t],t===pn&&(t=r),Y(t)?t.call(n):t},c.size=function(n){return null==n?0:(n=X(n)?n:on(n),n.length)},c.some=function(n,t,r){return t=r?pn:t,T(n,m(t))},c.uniqueId=function(n){var t=++Nn;return un(n)+t},c.each=V,c.first=P,ln(c,function(){var n={};return g(c,function(t,r){kn.call(c.prototype,r)||(n[r]=t)}),n}(),{chain:false}),c.VERSION="4.11.2",zn("pop join replace reverse split push shift sort splice unshift".split(" "),function(n){
+var t=(/^(?:replace|split)$/.test(n)?String.prototype:An)[n],r=/^(?:push|sort|unshift)$/.test(n)?"tap":"thru",e=/^(?:pop|join|replace|shift)$/.test(n);c.prototype[n]=function(){var n=arguments;if(e&&!this.__chain__){var u=this.value();return t.apply(Vn(u)?u:[],n)}return this[r](function(r){return t.apply(Vn(r)?r:[],n)})}}),c.prototype.toJSON=c.prototype.valueOf=c.prototype.value=function(){return F(this.__wrapped__,this.__actions__)},(wn||mn||{})._=c,typeof define=="function"&&typeof define.amd=="object"&&define.amd? define(function(){
+return c}):_n&&dn?(jn&&((dn.exports=c)._=c),_n._=c):On._=c}).call(this);
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.fp.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.fp.js
new file mode 100644
index 0000000..6181b35
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.fp.js
@@ -0,0 +1,863 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["fp"] = factory();
+	else
+		root["fp"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var baseConvert = __webpack_require__(1);
+
+	/**
+	 * Converts `lodash` to an immutable auto-curried iteratee-first data-last
+	 * version with conversion `options` applied.
+	 *
+	 * @param {Function} lodash The lodash function to convert.
+	 * @param {Object} [options] The options object. See `baseConvert` for more details.
+	 * @returns {Function} Returns the converted `lodash`.
+	 */
+	function browserConvert(lodash, options) {
+	  return baseConvert(lodash, lodash, options);
+	}
+
+	if (typeof _ == 'function') {
+	  _ = browserConvert(_.runInContext());
+	}
+	module.exports = browserConvert;
+
+
+/***/ },
+/* 1 */
+/***/ function(module, exports, __webpack_require__) {
+
+	var mapping = __webpack_require__(2),
+	    mutateMap = mapping.mutate,
+	    fallbackHolder = __webpack_require__(3);
+
+	/**
+	 * Creates a function, with an arity of `n`, that invokes `func` with the
+	 * arguments it receives.
+	 *
+	 * @private
+	 * @param {Function} func The function to wrap.
+	 * @param {number} n The arity of the new function.
+	 * @returns {Function} Returns the new function.
+	 */
+	function baseArity(func, n) {
+	  return n == 2
+	    ? function(a, b) { return func.apply(undefined, arguments); }
+	    : function(a) { return func.apply(undefined, arguments); };
+	}
+
+	/**
+	 * Creates a function that invokes `func`, with up to `n` arguments, ignoring
+	 * any additional arguments.
+	 *
+	 * @private
+	 * @param {Function} func The function to cap arguments for.
+	 * @param {number} n The arity cap.
+	 * @returns {Function} Returns the new function.
+	 */
+	function baseAry(func, n) {
+	  return n == 2
+	    ? function(a, b) { return func(a, b); }
+	    : function(a) { return func(a); };
+	}
+
+	/**
+	 * Creates a clone of `array`.
+	 *
+	 * @private
+	 * @param {Array} array The array to clone.
+	 * @returns {Array} Returns the cloned array.
+	 */
+	function cloneArray(array) {
+	  var length = array ? array.length : 0,
+	      result = Array(length);
+
+	  while (length--) {
+	    result[length] = array[length];
+	  }
+	  return result;
+	}
+
+	/**
+	 * Creates a function that clones a given object using the assignment `func`.
+	 *
+	 * @private
+	 * @param {Function} func The assignment function.
+	 * @returns {Function} Returns the new cloner function.
+	 */
+	function createCloner(func) {
+	  return function(object) {
+	    return func({}, object);
+	  };
+	}
+
+	/**
+	 * Creates a function that wraps `func` and uses `cloner` to clone the first
+	 * argument it receives.
+	 *
+	 * @private
+	 * @param {Function} func The function to wrap.
+	 * @param {Function} cloner The function to clone arguments.
+	 * @returns {Function} Returns the new immutable function.
+	 */
+	function immutWrap(func, cloner) {
+	  return function() {
+	    var length = arguments.length;
+	    if (!length) {
+	      return result;
+	    }
+	    var args = Array(length);
+	    while (length--) {
+	      args[length] = arguments[length];
+	    }
+	    var result = args[0] = cloner.apply(undefined, args);
+	    func.apply(undefined, args);
+	    return result;
+	  };
+	}
+
+	/**
+	 * The base implementation of `convert` which accepts a `util` object of methods
+	 * required to perform conversions.
+	 *
+	 * @param {Object} util The util object.
+	 * @param {string} name The name of the function to convert.
+	 * @param {Function} func The function to convert.
+	 * @param {Object} [options] The options object.
+	 * @param {boolean} [options.cap=true] Specify capping iteratee arguments.
+	 * @param {boolean} [options.curry=true] Specify currying.
+	 * @param {boolean} [options.fixed=true] Specify fixed arity.
+	 * @param {boolean} [options.immutable=true] Specify immutable operations.
+	 * @param {boolean} [options.rearg=true] Specify rearranging arguments.
+	 * @returns {Function|Object} Returns the converted function or object.
+	 */
+	function baseConvert(util, name, func, options) {
+	  var setPlaceholder,
+	      isLib = typeof name == 'function',
+	      isObj = name === Object(name);
+
+	  if (isObj) {
+	    options = func;
+	    func = name;
+	    name = undefined;
+	  }
+	  if (func == null) {
+	    throw new TypeError;
+	  }
+	  options || (options = {});
+
+	  var config = {
+	    'cap': 'cap' in options ? options.cap : true,
+	    'curry': 'curry' in options ? options.curry : true,
+	    'fixed': 'fixed' in options ? options.fixed : true,
+	    'immutable': 'immutable' in options ? options.immutable : true,
+	    'rearg': 'rearg' in options ? options.rearg : true
+	  };
+
+	  var forceCurry = ('curry' in options) && options.curry,
+	      forceFixed = ('fixed' in options) && options.fixed,
+	      forceRearg = ('rearg' in options) && options.rearg,
+	      placeholder = isLib ? func : fallbackHolder,
+	      pristine = isLib ? func.runInContext() : undefined;
+
+	  var helpers = isLib ? func : {
+	    'ary': util.ary,
+	    'assign': util.assign,
+	    'clone': util.clone,
+	    'curry': util.curry,
+	    'forEach': util.forEach,
+	    'isArray': util.isArray,
+	    'isFunction': util.isFunction,
+	    'iteratee': util.iteratee,
+	    'keys': util.keys,
+	    'rearg': util.rearg,
+	    'spread': util.spread,
+	    'toPath': util.toPath
+	  };
+
+	  var ary = helpers.ary,
+	      assign = helpers.assign,
+	      clone = helpers.clone,
+	      curry = helpers.curry,
+	      each = helpers.forEach,
+	      isArray = helpers.isArray,
+	      isFunction = helpers.isFunction,
+	      keys = helpers.keys,
+	      rearg = helpers.rearg,
+	      spread = helpers.spread,
+	      toPath = helpers.toPath;
+
+	  var aryMethodKeys = keys(mapping.aryMethod);
+
+	  var wrappers = {
+	    'castArray': function(castArray) {
+	      return function() {
+	        var value = arguments[0];
+	        return isArray(value)
+	          ? castArray(cloneArray(value))
+	          : castArray.apply(undefined, arguments);
+	      };
+	    },
+	    'iteratee': function(iteratee) {
+	      return function() {
+	        var func = arguments[0],
+	            arity = arguments[1],
+	            result = iteratee(func, arity),
+	            length = result.length;
+
+	        if (config.cap && typeof arity == 'number') {
+	          arity = arity > 2 ? (arity - 2) : 1;
+	          return (length && length <= arity) ? result : baseAry(result, arity);
+	        }
+	        return result;
+	      };
+	    },
+	    'mixin': function(mixin) {
+	      return function(source) {
+	        var func = this;
+	        if (!isFunction(func)) {
+	          return mixin(func, Object(source));
+	        }
+	        var methods = [],
+	            methodNames = [];
+
+	        each(keys(source), function(key) {
+	          var value = source[key];
+	          if (isFunction(value)) {
+	            methodNames.push(key);
+	            methods.push(func.prototype[key]);
+	          }
+	        });
+
+	        mixin(func, Object(source));
+
+	        each(methodNames, function(methodName, index) {
+	          var method = methods[index];
+	          if (isFunction(method)) {
+	            func.prototype[methodName] = method;
+	          } else {
+	            delete func.prototype[methodName];
+	          }
+	        });
+	        return func;
+	      };
+	    },
+	    'runInContext': function(runInContext) {
+	      return function(context) {
+	        return baseConvert(util, runInContext(context), options);
+	      };
+	    }
+	  };
+
+	  /*--------------------------------------------------------------------------*/
+
+	  /**
+	   * Creates a clone of `object` by `path`.
+	   *
+	   * @private
+	   * @param {Object} object The object to clone.
+	   * @param {Array|string} path The path to clone by.
+	   * @returns {Object} Returns the cloned object.
+	   */
+	  function cloneByPath(object, path) {
+	    path = toPath(path);
+
+	    var index = -1,
+	        length = path.length,
+	        result = clone(Object(object)),
+	        nested = result;
+
+	    while (nested != null && ++index < length) {
+	      var key = path[index],
+	          value = nested[key];
+
+	      if (value != null) {
+	        nested[key] = clone(Object(value));
+	      }
+	      nested = nested[key];
+	    }
+	    return result;
+	  }
+
+	  /**
+	   * Converts `lodash` to an immutable auto-curried iteratee-first data-last
+	   * version with conversion `options` applied.
+	   *
+	   * @param {Object} [options] The options object. See `baseConvert` for more details.
+	   * @returns {Function} Returns the converted `lodash`.
+	   */
+	  function convertLib(options) {
+	    return _.runInContext.convert(options)(undefined);
+	  }
+
+	  /**
+	   * Create a converter function for `func` of `name`.
+	   *
+	   * @param {string} name The name of the function to convert.
+	   * @param {Function} func The function to convert.
+	   * @returns {Function} Returns the new converter function.
+	   */
+	  function createConverter(name, func) {
+	    var oldOptions = options;
+	    return function(options) {
+	      var newUtil = isLib ? pristine : helpers,
+	          newFunc = isLib ? pristine[name] : func,
+	          newOptions = assign(assign({}, oldOptions), options);
+
+	      return baseConvert(newUtil, name, newFunc, newOptions);
+	    };
+	  }
+
+	  /**
+	   * Creates a function that wraps `func` to invoke its iteratee, with up to `n`
+	   * arguments, ignoring any additional arguments.
+	   *
+	   * @private
+	   * @param {Function} func The function to cap iteratee arguments for.
+	   * @param {number} n The arity cap.
+	   * @returns {Function} Returns the new function.
+	   */
+	  function iterateeAry(func, n) {
+	    return overArg(func, function(func) {
+	      return typeof func == 'function' ? baseAry(func, n) : func;
+	    });
+	  }
+
+	  /**
+	   * Creates a function that wraps `func` to invoke its iteratee with arguments
+	   * arranged according to the specified `indexes` where the argument value at
+	   * the first index is provided as the first argument, the argument value at
+	   * the second index is provided as the second argument, and so on.
+	   *
+	   * @private
+	   * @param {Function} func The function to rearrange iteratee arguments for.
+	   * @param {number[]} indexes The arranged argument indexes.
+	   * @returns {Function} Returns the new function.
+	   */
+	  function iterateeRearg(func, indexes) {
+	    return overArg(func, function(func) {
+	      var n = indexes.length;
+	      return baseArity(rearg(baseAry(func, n), indexes), n);
+	    });
+	  }
+
+	  /**
+	   * Creates a function that invokes `func` with its first argument passed
+	   * thru `transform`.
+	   *
+	   * @private
+	   * @param {Function} func The function to wrap.
+	   * @param {...Function} transform The functions to transform the first argument.
+	   * @returns {Function} Returns the new function.
+	   */
+	  function overArg(func, transform) {
+	    return function() {
+	      var length = arguments.length;
+	      if (!length) {
+	        return func();
+	      }
+	      var args = Array(length);
+	      while (length--) {
+	        args[length] = arguments[length];
+	      }
+	      var index = config.rearg ? 0 : (length - 1);
+	      args[index] = transform(args[index]);
+	      return func.apply(undefined, args);
+	    };
+	  }
+
+	  /**
+	   * Creates a function that wraps `func` and applys the conversions
+	   * rules by `name`.
+	   *
+	   * @private
+	   * @param {string} name The name of the function to wrap.
+	   * @param {Function} func The function to wrap.
+	   * @returns {Function} Returns the converted function.
+	   */
+	  function wrap(name, func) {
+	    name = mapping.aliasToReal[name] || name;
+
+	    var result,
+	        wrapped = func,
+	        wrapper = wrappers[name];
+
+	    if (wrapper) {
+	      wrapped = wrapper(func);
+	    }
+	    else if (config.immutable) {
+	      if (mutateMap.array[name]) {
+	        wrapped = immutWrap(func, cloneArray);
+	      }
+	      else if (mutateMap.object[name]) {
+	        wrapped = immutWrap(func, createCloner(func));
+	      }
+	      else if (mutateMap.set[name]) {
+	        wrapped = immutWrap(func, cloneByPath);
+	      }
+	    }
+	    each(aryMethodKeys, function(aryKey) {
+	      each(mapping.aryMethod[aryKey], function(otherName) {
+	        if (name == otherName) {
+	          var aryN = !isLib && mapping.iterateeAry[name],
+	              reargIndexes = mapping.iterateeRearg[name],
+	              spreadStart = mapping.methodSpread[name];
+
+	          result = wrapped;
+	          if (config.fixed && (forceFixed || !mapping.skipFixed[name])) {
+	            result = spreadStart === undefined
+	              ? ary(result, aryKey)
+	              : spread(result, spreadStart);
+	          }
+	          if (config.rearg && aryKey > 1 && (forceRearg || !mapping.skipRearg[name])) {
+	            result = rearg(result, mapping.methodRearg[name] || mapping.aryRearg[aryKey]);
+	          }
+	          if (config.cap) {
+	            if (reargIndexes) {
+	              result = iterateeRearg(result, reargIndexes);
+	            } else if (aryN) {
+	              result = iterateeAry(result, aryN);
+	            }
+	          }
+	          if (forceCurry || (config.curry && aryKey > 1)) {
+	            forceCurry  && console.log(forceCurry, name);
+	            result = curry(result, aryKey);
+	          }
+	          return false;
+	        }
+	      });
+	      return !result;
+	    });
+
+	    result || (result = wrapped);
+	    if (result == func) {
+	      result = forceCurry ? curry(result, 1) : function() {
+	        return func.apply(this, arguments);
+	      };
+	    }
+	    result.convert = createConverter(name, func);
+	    if (mapping.placeholder[name]) {
+	      setPlaceholder = true;
+	      result.placeholder = func.placeholder = placeholder;
+	    }
+	    return result;
+	  }
+
+	  /*--------------------------------------------------------------------------*/
+
+	  if (!isObj) {
+	    return wrap(name, func);
+	  }
+	  var _ = func;
+
+	  // Convert methods by ary cap.
+	  var pairs = [];
+	  each(aryMethodKeys, function(aryKey) {
+	    each(mapping.aryMethod[aryKey], function(key) {
+	      var func = _[mapping.remap[key] || key];
+	      if (func) {
+	        pairs.push([key, wrap(key, func)]);
+	      }
+	    });
+	  });
+
+	  // Convert remaining methods.
+	  each(keys(_), function(key) {
+	    var func = _[key];
+	    if (typeof func == 'function') {
+	      var length = pairs.length;
+	      while (length--) {
+	        if (pairs[length][0] == key) {
+	          return;
+	        }
+	      }
+	      func.convert = createConverter(key, func);
+	      pairs.push([key, func]);
+	    }
+	  });
+
+	  // Assign to `_` leaving `_.prototype` unchanged to allow chaining.
+	  each(pairs, function(pair) {
+	    _[pair[0]] = pair[1];
+	  });
+
+	  _.convert = convertLib;
+	  if (setPlaceholder) {
+	    _.placeholder = placeholder;
+	  }
+	  // Assign aliases.
+	  each(keys(_), function(key) {
+	    each(mapping.realToAlias[key] || [], function(alias) {
+	      _[alias] = _[key];
+	    });
+	  });
+
+	  return _;
+	}
+
+	module.exports = baseConvert;
+
+
+/***/ },
+/* 2 */
+/***/ function(module, exports) {
+
+	/** Used to map aliases to their real names. */
+	exports.aliasToReal = {
+
+	  // Lodash aliases.
+	  'each': 'forEach',
+	  'eachRight': 'forEachRight',
+	  'entries': 'toPairs',
+	  'entriesIn': 'toPairsIn',
+	  'extend': 'assignIn',
+	  'extendWith': 'assignInWith',
+	  'first': 'head',
+
+	  // Ramda aliases.
+	  '__': 'placeholder',
+	  'all': 'every',
+	  'allPass': 'overEvery',
+	  'always': 'constant',
+	  'any': 'some',
+	  'anyPass': 'overSome',
+	  'apply': 'spread',
+	  'assoc': 'set',
+	  'assocPath': 'set',
+	  'complement': 'negate',
+	  'compose': 'flowRight',
+	  'contains': 'includes',
+	  'dissoc': 'unset',
+	  'dissocPath': 'unset',
+	  'equals': 'isEqual',
+	  'identical': 'eq',
+	  'init': 'initial',
+	  'invertObj': 'invert',
+	  'juxt': 'over',
+	  'omitAll': 'omit',
+	  'nAry': 'ary',
+	  'path': 'get',
+	  'pathEq': 'matchesProperty',
+	  'pathOr': 'getOr',
+	  'paths': 'at',
+	  'pickAll': 'pick',
+	  'pipe': 'flow',
+	  'pluck': 'map',
+	  'prop': 'get',
+	  'propEq': 'matchesProperty',
+	  'propOr': 'getOr',
+	  'props': 'at',
+	  'unapply': 'rest',
+	  'unnest': 'flatten',
+	  'useWith': 'overArgs',
+	  'whereEq': 'filter',
+	  'zipObj': 'zipObject'
+	};
+
+	/** Used to map ary to method names. */
+	exports.aryMethod = {
+	  '1': [
+	    'attempt', 'castArray', 'ceil', 'create', 'curry', 'curryRight', 'floor',
+	    'flow', 'flowRight', 'fromPairs', 'invert', 'iteratee', 'memoize', 'method',
+	    'methodOf', 'mixin', 'over', 'overEvery', 'overSome', 'rest', 'reverse',
+	    'round', 'runInContext', 'spread', 'template', 'trim', 'trimEnd', 'trimStart',
+	    'uniqueId', 'words'
+	  ],
+	  '2': [
+	    'add', 'after', 'ary', 'assign', 'assignIn', 'at', 'before', 'bind', 'bindAll',
+	    'bindKey', 'chunk', 'cloneDeepWith', 'cloneWith', 'concat', 'countBy', 'curryN',
+	    'curryRightN', 'debounce', 'defaults', 'defaultsDeep', 'delay', 'difference',
+	    'divide', 'drop', 'dropRight', 'dropRightWhile', 'dropWhile', 'endsWith',
+	    'eq', 'every', 'filter', 'find', 'find', 'findIndex', 'findKey', 'findLast',
+	    'findLastIndex', 'findLastKey', 'flatMap', 'flatMapDeep', 'flattenDepth',
+	    'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight',
+	    'get', 'groupBy', 'gt', 'gte', 'has', 'hasIn', 'includes', 'indexOf',
+	    'intersection', 'invertBy', 'invoke', 'invokeMap', 'isEqual', 'isMatch',
+	    'join', 'keyBy', 'lastIndexOf', 'lt', 'lte', 'map', 'mapKeys', 'mapValues',
+	    'matchesProperty', 'maxBy', 'meanBy', 'merge', 'minBy', 'multiply', 'nth',
+	    'omit', 'omitBy', 'overArgs', 'pad', 'padEnd', 'padStart', 'parseInt',
+	    'partial', 'partialRight', 'partition', 'pick', 'pickBy', 'pull', 'pullAll',
+	    'pullAt', 'random', 'range', 'rangeRight', 'rearg', 'reject', 'remove',
+	    'repeat', 'restFrom', 'result', 'sampleSize', 'some', 'sortBy', 'sortedIndex',
+	    'sortedIndexOf', 'sortedLastIndex', 'sortedLastIndexOf', 'sortedUniqBy',
+	    'split', 'spreadFrom', 'startsWith', 'subtract', 'sumBy', 'take', 'takeRight',
+	    'takeRightWhile', 'takeWhile', 'tap', 'throttle', 'thru', 'times', 'trimChars',
+	    'trimCharsEnd', 'trimCharsStart', 'truncate', 'union', 'uniqBy', 'uniqWith',
+	    'unset', 'unzipWith', 'without', 'wrap', 'xor', 'zip', 'zipObject',
+	    'zipObjectDeep'
+	  ],
+	  '3': [
+	    'assignInWith', 'assignWith', 'clamp', 'differenceBy', 'differenceWith',
+	    'getOr', 'inRange', 'intersectionBy', 'intersectionWith', 'invokeArgs',
+	    'invokeArgsMap', 'isEqualWith', 'isMatchWith', 'flatMapDepth', 'mergeWith',
+	    'orderBy', 'padChars', 'padCharsEnd', 'padCharsStart', 'pullAllBy',
+	    'pullAllWith', 'reduce', 'reduceRight', 'replace', 'set', 'slice',
+	    'sortedIndexBy', 'sortedLastIndexBy', 'transform', 'unionBy', 'unionWith',
+	    'update', 'xorBy', 'xorWith', 'zipWith'
+	  ],
+	  '4': [
+	    'fill', 'setWith', 'updateWith'
+	  ]
+	};
+
+	/** Used to map ary to rearg configs. */
+	exports.aryRearg = {
+	  '2': [1, 0],
+	  '3': [2, 0, 1],
+	  '4': [3, 2, 0, 1]
+	};
+
+	/** Used to map method names to their iteratee ary. */
+	exports.iterateeAry = {
+	  'dropRightWhile': 1,
+	  'dropWhile': 1,
+	  'every': 1,
+	  'filter': 1,
+	  'find': 1,
+	  'findIndex': 1,
+	  'findKey': 1,
+	  'findLast': 1,
+	  'findLastIndex': 1,
+	  'findLastKey': 1,
+	  'flatMap': 1,
+	  'flatMapDeep': 1,
+	  'flatMapDepth': 1,
+	  'forEach': 1,
+	  'forEachRight': 1,
+	  'forIn': 1,
+	  'forInRight': 1,
+	  'forOwn': 1,
+	  'forOwnRight': 1,
+	  'map': 1,
+	  'mapKeys': 1,
+	  'mapValues': 1,
+	  'partition': 1,
+	  'reduce': 2,
+	  'reduceRight': 2,
+	  'reject': 1,
+	  'remove': 1,
+	  'some': 1,
+	  'takeRightWhile': 1,
+	  'takeWhile': 1,
+	  'times': 1,
+	  'transform': 2
+	};
+
+	/** Used to map method names to iteratee rearg configs. */
+	exports.iterateeRearg = {
+	  'mapKeys': [1]
+	};
+
+	/** Used to map method names to rearg configs. */
+	exports.methodRearg = {
+	  'assignInWith': [1, 2, 0],
+	  'assignWith': [1, 2, 0],
+	  'getOr': [2, 1, 0],
+	  'isEqualWith': [1, 2, 0],
+	  'isMatchWith': [2, 1, 0],
+	  'mergeWith': [1, 2, 0],
+	  'padChars': [2, 1, 0],
+	  'padCharsEnd': [2, 1, 0],
+	  'padCharsStart': [2, 1, 0],
+	  'pullAllBy': [2, 1, 0],
+	  'pullAllWith': [2, 1, 0],
+	  'setWith': [3, 1, 2, 0],
+	  'sortedIndexBy': [2, 1, 0],
+	  'sortedLastIndexBy': [2, 1, 0],
+	  'updateWith': [3, 1, 2, 0],
+	  'zipWith': [1, 2, 0]
+	};
+
+	/** Used to map method names to spread configs. */
+	exports.methodSpread = {
+	  'invokeArgs': 2,
+	  'invokeArgsMap': 2,
+	  'partial': 1,
+	  'partialRight': 1,
+	  'without': 1
+	};
+
+	/** Used to identify methods which mutate arrays or objects. */
+	exports.mutate = {
+	  'array': {
+	    'fill': true,
+	    'pull': true,
+	    'pullAll': true,
+	    'pullAllBy': true,
+	    'pullAllWith': true,
+	    'pullAt': true,
+	    'remove': true,
+	    'reverse': true
+	  },
+	  'object': {
+	    'assign': true,
+	    'assignIn': true,
+	    'assignInWith': true,
+	    'assignWith': true,
+	    'defaults': true,
+	    'defaultsDeep': true,
+	    'merge': true,
+	    'mergeWith': true
+	  },
+	  'set': {
+	    'set': true,
+	    'setWith': true,
+	    'unset': true,
+	    'update': true,
+	    'updateWith': true
+	  }
+	};
+
+	/** Used to track methods with placeholder support */
+	exports.placeholder = {
+	  'bind': true,
+	  'bindKey': true,
+	  'curry': true,
+	  'curryRight': true,
+	  'partial': true,
+	  'partialRight': true
+	};
+
+	/** Used to map real names to their aliases. */
+	exports.realToAlias = (function() {
+	  var hasOwnProperty = Object.prototype.hasOwnProperty,
+	      object = exports.aliasToReal,
+	      result = {};
+
+	  for (var key in object) {
+	    var value = object[key];
+	    if (hasOwnProperty.call(result, value)) {
+	      result[value].push(key);
+	    } else {
+	      result[value] = [key];
+	    }
+	  }
+	  return result;
+	}());
+
+	/** Used to map method names to other names. */
+	exports.remap = {
+	  'curryN': 'curry',
+	  'curryRightN': 'curryRight',
+	  'getOr': 'get',
+	  'invokeArgs': 'invoke',
+	  'invokeArgsMap': 'invokeMap',
+	  'padChars': 'pad',
+	  'padCharsEnd': 'padEnd',
+	  'padCharsStart': 'padStart',
+	  'restFrom': 'rest',
+	  'spreadFrom': 'spread',
+	  'trimChars': 'trim',
+	  'trimCharsEnd': 'trimEnd',
+	  'trimCharsStart': 'trimStart'
+	};
+
+	/** Used to track methods that skip fixing their arity. */
+	exports.skipFixed = {
+	  'castArray': true,
+	  'flow': true,
+	  'flowRight': true,
+	  'iteratee': true,
+	  'mixin': true,
+	  'runInContext': true
+	};
+
+	/** Used to track methods that skip rearranging arguments. */
+	exports.skipRearg = {
+	  'add': true,
+	  'assign': true,
+	  'assignIn': true,
+	  'bind': true,
+	  'bindKey': true,
+	  'concat': true,
+	  'difference': true,
+	  'divide': true,
+	  'eq': true,
+	  'gt': true,
+	  'gte': true,
+	  'isEqual': true,
+	  'lt': true,
+	  'lte': true,
+	  'matchesProperty': true,
+	  'merge': true,
+	  'multiply': true,
+	  'overArgs': true,
+	  'partial': true,
+	  'partialRight': true,
+	  'random': true,
+	  'range': true,
+	  'rangeRight': true,
+	  'subtract': true,
+	  'without': true,
+	  'zip': true,
+	  'zipObject': true
+	};
+
+
+/***/ },
+/* 3 */
+/***/ function(module, exports) {
+
+	/**
+	 * The default argument placeholder value for methods.
+	 *
+	 * @type {Object}
+	 */
+	module.exports = {};
+
+
+/***/ }
+/******/ ])
+});
+;
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.fp.min.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.fp.min.js
new file mode 100644
index 0000000..bd12b9e
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.fp.min.js
@@ -0,0 +1,16 @@
+!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.fp=e():t.fp=e()}(this,function(){return function(t){function e(n){if(r[n])return r[n].exports;var i=r[n]={exports:{},id:n,loaded:!1};return t[n].call(i.exports,i,i.exports,e),i.loaded=!0,i.exports}var r={};return e.m=t,e.c=r,e.p="",e(0)}([function(t,e,r){function n(t,e){return i(t,t,e)}var i=r(1);"function"==typeof _&&(_=n(_.runInContext())),
+t.exports=n},function(t,e,r){function n(t,e){return 2==e?function(e,r){return t.apply(void 0,arguments)}:function(e){return t.apply(void 0,arguments)}}function i(t,e){return 2==e?function(e,r){return t(e,r)}:function(e){return t(e)}}function a(t){for(var e=t?t.length:0,r=Array(e);e--;)r[e]=t[e];return r}function o(t){return function(e){return t({},e)}}function s(t,e){return function(){var r=arguments.length;if(!r)return i;for(var n=Array(r);r--;)n[r]=arguments[r];var i=n[0]=e.apply(void 0,n);return t.apply(void 0,n),
+i}}function u(t,e,r,d){function f(t,e){e=F(e);for(var r=-1,n=e.length,i=M(Object(t)),a=i;null!=a&&++r<n;){var o=e[r],s=a[o];null!=s&&(a[o]=M(Object(s))),a=a[o]}return i}function h(t){return N.runInContext.convert(t)(void 0)}function y(t,e){var r=d;return function(n){var i=R?B:j,a=R?B[t]:e,o=w(w({},r),n);return u(i,t,a,o)}}function g(t,e){return v(t,function(t){return"function"==typeof t?i(t,e):t})}function m(t,e){return v(t,function(t){var r=e.length;return n(L(i(t,r),e),r)})}function v(t,e){return function(){
+var r=arguments.length;if(!r)return t();for(var n=Array(r);r--;)n[r]=arguments[r];var i=I.rearg?0:r-1;return n[i]=e(n[i]),t.apply(void 0,n)}}function x(t,e){t=p.aliasToReal[t]||t;var r,n=e,i=_[t];return i?n=i(e):I.immutable&&(l.array[t]?n=s(e,a):l.object[t]?n=s(e,o(e)):l.set[t]&&(n=s(e,f))),P(T,function(e){return P(p.aryMethod[e],function(i){if(t==i){var a=!R&&p.iterateeAry[t],o=p.iterateeRearg[t],s=p.methodSpread[t];return r=n,!I.fixed||!k&&p.skipFixed[t]||(r=void 0===s?C(r,e):D(r,s)),I.rearg&&e>1&&(E||!p.skipRearg[t])&&(r=L(r,p.methodRearg[t]||p.aryRearg[e])),
+I.cap&&(o?r=m(r,o):a&&(r=g(r,a))),(b||I.curry&&e>1)&&(b&&console.log(b,t),r=q(r,e)),!1}}),!r}),r||(r=n),r==e&&(r=b?q(r,1):function(){return e.apply(this,arguments)}),r.convert=y(t,e),p.placeholder[t]&&(W=!0,r.placeholder=e.placeholder=O),r}var W,R="function"==typeof e,A=e===Object(e);if(A&&(d=r,r=e,e=void 0),null==r)throw new TypeError;d||(d={});var I={cap:"cap"in d?d.cap:!0,curry:"curry"in d?d.curry:!0,fixed:"fixed"in d?d.fixed:!0,immutable:"immutable"in d?d.immutable:!0,rearg:"rearg"in d?d.rearg:!0
+},b="curry"in d&&d.curry,k="fixed"in d&&d.fixed,E="rearg"in d&&d.rearg,O=R?r:c,B=R?r.runInContext():void 0,j=R?r:{ary:t.ary,assign:t.assign,clone:t.clone,curry:t.curry,forEach:t.forEach,isArray:t.isArray,isFunction:t.isFunction,iteratee:t.iteratee,keys:t.keys,rearg:t.rearg,spread:t.spread,toPath:t.toPath},C=j.ary,w=j.assign,M=j.clone,q=j.curry,P=j.forEach,S=j.isArray,z=j.isFunction,K=j.keys,L=j.rearg,D=j.spread,F=j.toPath,T=K(p.aryMethod),_={castArray:function(t){return function(){var e=arguments[0];
+return S(e)?t(a(e)):t.apply(void 0,arguments)}},iteratee:function(t){return function(){var e=arguments[0],r=arguments[1],n=t(e,r),a=n.length;return I.cap&&"number"==typeof r?(r=r>2?r-2:1,a&&r>=a?n:i(n,r)):n}},mixin:function(t){return function(e){var r=this;if(!z(r))return t(r,Object(e));var n=[],i=[];return P(K(e),function(t){var a=e[t];z(a)&&(i.push(t),n.push(r.prototype[t]))}),t(r,Object(e)),P(i,function(t,e){var i=n[e];z(i)?r.prototype[t]=i:delete r.prototype[t]}),r}},runInContext:function(e){
+return function(r){return u(t,e(r),d)}}};if(!A)return x(e,r);var N=r,V=[];return P(T,function(t){P(p.aryMethod[t],function(t){var e=N[p.remap[t]||t];e&&V.push([t,x(t,e)])})}),P(K(N),function(t){var e=N[t];if("function"==typeof e){for(var r=V.length;r--;)if(V[r][0]==t)return;e.convert=y(t,e),V.push([t,e])}}),P(V,function(t){N[t[0]]=t[1]}),N.convert=h,W&&(N.placeholder=O),P(K(N),function(t){P(p.realToAlias[t]||[],function(e){N[e]=N[t]})}),N}var p=r(2),l=p.mutate,c=r(3);t.exports=u},function(t,e){e.aliasToReal={
+each:"forEach",eachRight:"forEachRight",entries:"toPairs",entriesIn:"toPairsIn",extend:"assignIn",extendWith:"assignInWith",first:"head",__:"placeholder",all:"every",allPass:"overEvery",always:"constant",any:"some",anyPass:"overSome",apply:"spread",assoc:"set",assocPath:"set",complement:"negate",compose:"flowRight",contains:"includes",dissoc:"unset",dissocPath:"unset",equals:"isEqual",identical:"eq",init:"initial",invertObj:"invert",juxt:"over",omitAll:"omit",nAry:"ary",path:"get",pathEq:"matchesProperty",
+pathOr:"getOr",paths:"at",pickAll:"pick",pipe:"flow",pluck:"map",prop:"get",propEq:"matchesProperty",propOr:"getOr",props:"at",unapply:"rest",unnest:"flatten",useWith:"overArgs",whereEq:"filter",zipObj:"zipObject"},e.aryMethod={1:["attempt","castArray","ceil","create","curry","curryRight","floor","flow","flowRight","fromPairs","invert","iteratee","memoize","method","methodOf","mixin","over","overEvery","overSome","rest","reverse","round","runInContext","spread","template","trim","trimEnd","trimStart","uniqueId","words"],
+2:["add","after","ary","assign","assignIn","at","before","bind","bindAll","bindKey","chunk","cloneDeepWith","cloneWith","concat","countBy","curryN","curryRightN","debounce","defaults","defaultsDeep","delay","difference","divide","drop","dropRight","dropRightWhile","dropWhile","endsWith","eq","every","filter","find","find","findIndex","findKey","findLast","findLastIndex","findLastKey","flatMap","flatMapDeep","flattenDepth","forEach","forEachRight","forIn","forInRight","forOwn","forOwnRight","get","groupBy","gt","gte","has","hasIn","includes","indexOf","intersection","invertBy","invoke","invokeMap","isEqual","isMatch","join","keyBy","lastIndexOf","lt","lte","map","mapKeys","mapValues","matchesProperty","maxBy","meanBy","merge","minBy","multiply","nth","omit","omitBy","overArgs","pad","padEnd","padStart","parseInt","partial","partialRight","partition","pick","pickBy","pull","pullAll","pullAt","random","range","rangeRight","rearg","reject","remove","repeat","restFrom","result","sampleSize","some","sortBy","sortedIndex","sortedIndexOf","sortedLastIndex","sortedLastIndexOf","sortedUniqBy","split","spreadFrom","startsWith","subtract","sumBy","take","takeRight","takeRightWhile","takeWhile","tap","throttle","thru","times","trimChars","trimCharsEnd","trimCharsStart","truncate","union","uniqBy","uniqWith","unset","unzipWith","without","wrap","xor","zip","zipObject","zipObjectDeep"],
+3:["assignInWith","assignWith","clamp","differenceBy","differenceWith","getOr","inRange","intersectionBy","intersectionWith","invokeArgs","invokeArgsMap","isEqualWith","isMatchWith","flatMapDepth","mergeWith","orderBy","padChars","padCharsEnd","padCharsStart","pullAllBy","pullAllWith","reduce","reduceRight","replace","set","slice","sortedIndexBy","sortedLastIndexBy","transform","unionBy","unionWith","update","xorBy","xorWith","zipWith"],4:["fill","setWith","updateWith"]},e.aryRearg={2:[1,0],3:[2,0,1],
+4:[3,2,0,1]},e.iterateeAry={dropRightWhile:1,dropWhile:1,every:1,filter:1,find:1,findIndex:1,findKey:1,findLast:1,findLastIndex:1,findLastKey:1,flatMap:1,flatMapDeep:1,flatMapDepth:1,forEach:1,forEachRight:1,forIn:1,forInRight:1,forOwn:1,forOwnRight:1,map:1,mapKeys:1,mapValues:1,partition:1,reduce:2,reduceRight:2,reject:1,remove:1,some:1,takeRightWhile:1,takeWhile:1,times:1,transform:2},e.iterateeRearg={mapKeys:[1]},e.methodRearg={assignInWith:[1,2,0],assignWith:[1,2,0],getOr:[2,1,0],isEqualWith:[1,2,0],
+isMatchWith:[2,1,0],mergeWith:[1,2,0],padChars:[2,1,0],padCharsEnd:[2,1,0],padCharsStart:[2,1,0],pullAllBy:[2,1,0],pullAllWith:[2,1,0],setWith:[3,1,2,0],sortedIndexBy:[2,1,0],sortedLastIndexBy:[2,1,0],updateWith:[3,1,2,0],zipWith:[1,2,0]},e.methodSpread={invokeArgs:2,invokeArgsMap:2,partial:1,partialRight:1,without:1},e.mutate={array:{fill:!0,pull:!0,pullAll:!0,pullAllBy:!0,pullAllWith:!0,pullAt:!0,remove:!0,reverse:!0},object:{assign:!0,assignIn:!0,assignInWith:!0,assignWith:!0,defaults:!0,defaultsDeep:!0,
+merge:!0,mergeWith:!0},set:{set:!0,setWith:!0,unset:!0,update:!0,updateWith:!0}},e.placeholder={bind:!0,bindKey:!0,curry:!0,curryRight:!0,partial:!0,partialRight:!0},e.realToAlias=function(){var t=Object.prototype.hasOwnProperty,r=e.aliasToReal,n={};for(var i in r){var a=r[i];t.call(n,a)?n[a].push(i):n[a]=[i]}return n}(),e.remap={curryN:"curry",curryRightN:"curryRight",getOr:"get",invokeArgs:"invoke",invokeArgsMap:"invokeMap",padChars:"pad",padCharsEnd:"padEnd",padCharsStart:"padStart",restFrom:"rest",
+spreadFrom:"spread",trimChars:"trim",trimCharsEnd:"trimEnd",trimCharsStart:"trimStart"},e.skipFixed={castArray:!0,flow:!0,flowRight:!0,iteratee:!0,mixin:!0,runInContext:!0},e.skipRearg={add:!0,assign:!0,assignIn:!0,bind:!0,bindKey:!0,concat:!0,difference:!0,divide:!0,eq:!0,gt:!0,gte:!0,isEqual:!0,lt:!0,lte:!0,matchesProperty:!0,merge:!0,multiply:!0,overArgs:!0,partial:!0,partialRight:!0,random:!0,range:!0,rangeRight:!0,subtract:!0,without:!0,zip:!0,zipObject:!0}},function(t,e){t.exports={}}])});
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.js
new file mode 100644
index 0000000..7bc771f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.js
@@ -0,0 +1,16149 @@
+/**
+ * @license
+ * lodash 4.11.2 (Custom Build) <https://lodash.com/>
+ * Build: `lodash -o ./dist/lodash.js`
+ * Copyright jQuery Foundation and other contributors <https://jquery.org/>
+ * Released under MIT license <https://lodash.com/license>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the semantic version number. */
+  var VERSION = '4.11.2';
+
+  /** Used as the size to enable large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Used as the `TypeError` message for "Functions" methods. */
+  var FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used to stand-in for `undefined` hash values. */
+  var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+  /** Used as the internal argument placeholder. */
+  var PLACEHOLDER = '__lodash_placeholder__';
+
+  /** Used to compose bitmasks for wrapper metadata. */
+  var BIND_FLAG = 1,
+      BIND_KEY_FLAG = 2,
+      CURRY_BOUND_FLAG = 4,
+      CURRY_FLAG = 8,
+      CURRY_RIGHT_FLAG = 16,
+      PARTIAL_FLAG = 32,
+      PARTIAL_RIGHT_FLAG = 64,
+      ARY_FLAG = 128,
+      REARG_FLAG = 256,
+      FLIP_FLAG = 512;
+
+  /** Used to compose bitmasks for comparison styles. */
+  var UNORDERED_COMPARE_FLAG = 1,
+      PARTIAL_COMPARE_FLAG = 2;
+
+  /** Used as default options for `_.truncate`. */
+  var DEFAULT_TRUNC_LENGTH = 30,
+      DEFAULT_TRUNC_OMISSION = '...';
+
+  /** Used to detect hot functions by number of calls within a span of milliseconds. */
+  var HOT_COUNT = 150,
+      HOT_SPAN = 16;
+
+  /** Used to indicate the type of lazy iteratees. */
+  var LAZY_FILTER_FLAG = 1,
+      LAZY_MAP_FLAG = 2,
+      LAZY_WHILE_FLAG = 3;
+
+  /** Used as references for various `Number` constants. */
+  var INFINITY = 1 / 0,
+      MAX_SAFE_INTEGER = 9007199254740991,
+      MAX_INTEGER = 1.7976931348623157e+308,
+      NAN = 0 / 0;
+
+  /** Used as references for the maximum length and index of an array. */
+  var MAX_ARRAY_LENGTH = 4294967295,
+      MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+      HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+  /** `Object#toString` result references. */
+  var argsTag = '[object Arguments]',
+      arrayTag = '[object Array]',
+      boolTag = '[object Boolean]',
+      dateTag = '[object Date]',
+      errorTag = '[object Error]',
+      funcTag = '[object Function]',
+      genTag = '[object GeneratorFunction]',
+      mapTag = '[object Map]',
+      numberTag = '[object Number]',
+      objectTag = '[object Object]',
+      promiseTag = '[object Promise]',
+      regexpTag = '[object RegExp]',
+      setTag = '[object Set]',
+      stringTag = '[object String]',
+      symbolTag = '[object Symbol]',
+      weakMapTag = '[object WeakMap]',
+      weakSetTag = '[object WeakSet]';
+
+  var arrayBufferTag = '[object ArrayBuffer]',
+      dataViewTag = '[object DataView]',
+      float32Tag = '[object Float32Array]',
+      float64Tag = '[object Float64Array]',
+      int8Tag = '[object Int8Array]',
+      int16Tag = '[object Int16Array]',
+      int32Tag = '[object Int32Array]',
+      uint8Tag = '[object Uint8Array]',
+      uint8ClampedTag = '[object Uint8ClampedArray]',
+      uint16Tag = '[object Uint16Array]',
+      uint32Tag = '[object Uint32Array]';
+
+  /** 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 HTML entities and HTML characters. */
+  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
+      reUnescapedHtml = /[&<>"'`]/g,
+      reHasEscapedHtml = RegExp(reEscapedHtml.source),
+      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+  /** Used to match template delimiters. */
+  var reEscape = /<%-([\s\S]+?)%>/g,
+      reEvaluate = /<%([\s\S]+?)%>/g,
+      reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match property names within property paths. */
+  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+      reIsPlainProp = /^\w*$/,
+      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]/g;
+
+  /**
+   * Used to match `RegExp`
+   * [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns).
+   */
+  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+      reHasRegExpChar = RegExp(reRegExpChar.source);
+
+  /** Used to match leading and trailing whitespace. */
+  var reTrim = /^\s+|\s+$/g,
+      reTrimStart = /^\s+/,
+      reTrimEnd = /\s+$/;
+
+  /** Used to match non-compound words composed of alphanumeric characters. */
+  var reBasicWord = /[a-zA-Z0-9]+/g;
+
+  /** Used to match backslashes in property paths. */
+  var reEscapeChar = /\\(\\)?/g;
+
+  /**
+   * Used to match
+   * [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components).
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match `RegExp` flags from their coerced string values. */
+  var reFlags = /\w*$/;
+
+  /** Used to detect hexadecimal string values. */
+  var reHasHexPrefix = /^0x/i;
+
+  /** Used to detect bad signed hexadecimal string values. */
+  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+  /** Used to detect binary string values. */
+  var reIsBinary = /^0b[01]+$/i;
+
+  /** Used to detect host constructors (Safari). */
+  var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+  /** Used to detect octal string values. */
+  var reIsOctal = /^0o[0-7]+$/i;
+
+  /** Used to detect unsigned integer values. */
+  var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+  /** Used to match latin-1 supplementary letters (excluding mathematical operators). */
+  var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;
+
+  /** Used to ensure capturing order of template delimiters. */
+  var reNoMatch = /($^)/;
+
+  /** Used to match unescaped characters in compiled string literals. */
+  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+  /** Used to compose unicode character classes. */
+  var rsAstralRange = '\\ud800-\\udfff',
+      rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23',
+      rsComboSymbolsRange = '\\u20d0-\\u20f0',
+      rsDingbatRange = '\\u2700-\\u27bf',
+      rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+      rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+      rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+      rsPunctuationRange = '\\u2000-\\u206f',
+      rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
+      rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+      rsVarRange = '\\ufe0e\\ufe0f',
+      rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
+
+  /** Used to compose unicode capture groups. */
+  var rsApos = "['\u2019]",
+      rsAstral = '[' + rsAstralRange + ']',
+      rsBreak = '[' + rsBreakRange + ']',
+      rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']',
+      rsDigits = '\\d+',
+      rsDingbat = '[' + rsDingbatRange + ']',
+      rsLower = '[' + rsLowerRange + ']',
+      rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+      rsFitz = '\\ud83c[\\udffb-\\udfff]',
+      rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+      rsNonAstral = '[^' + rsAstralRange + ']',
+      rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
+      rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
+      rsUpper = '[' + rsUpperRange + ']',
+      rsZWJ = '\\u200d';
+
+  /** Used to compose unicode regexes. */
+  var rsLowerMisc = '(?:' + rsLower + '|' + rsMisc + ')',
+      rsUpperMisc = '(?:' + rsUpper + '|' + rsMisc + ')',
+      rsOptLowerContr = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
+      rsOptUpperContr = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
+      reOptMod = rsModifier + '?',
+      rsOptVar = '[' + rsVarRange + ']?',
+      rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+      rsSeq = rsOptVar + reOptMod + rsOptJoin,
+      rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+      rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+
+  /** Used to match apostrophes. */
+  var reApos = RegExp(rsApos, 'g');
+
+  /**
+   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+   */
+  var reComboMark = RegExp(rsCombo, 'g');
+
+  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+  var reComplexSymbol = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+
+  /** Used to match complex or compound words. */
+  var reComplexWord = RegExp([
+    rsUpper + '?' + rsLower + '+' + rsOptLowerContr + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
+    rsUpperMisc + '+' + rsOptUpperContr + '(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')',
+    rsUpper + '?' + rsLowerMisc + '+' + rsOptLowerContr,
+    rsUpper + '+' + rsOptUpperContr,
+    rsDigits,
+    rsEmoji
+  ].join('|'), 'g');
+
+  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+  var reHasComplexSymbol = RegExp('[' + rsZWJ + rsAstralRange  + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']');
+
+  /** Used to detect strings that need a more robust regexp to match words. */
+  var reHasComplexWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+
+  /** Used to assign default `context` object properties. */
+  var contextProps = [
+    'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
+    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
+    'Promise', 'Reflect', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError',
+    'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
+    '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
+  ];
+
+  /** Used to make template sourceURLs easier to identify. */
+  var templateCounter = -1;
+
+  /** Used to identify `toStringTag` values of typed arrays. */
+  var typedArrayTags = {};
+  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+  typedArrayTags[uint32Tag] = true;
+  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+  typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
+  typedArrayTags[errorTag] = typedArrayTags[funcTag] =
+  typedArrayTags[mapTag] = typedArrayTags[numberTag] =
+  typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
+  typedArrayTags[setTag] = typedArrayTags[stringTag] =
+  typedArrayTags[weakMapTag] = false;
+
+  /** Used to identify `toStringTag` values supported by `_.clone`. */
+  var cloneableTags = {};
+  cloneableTags[argsTag] = cloneableTags[arrayTag] =
+  cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
+  cloneableTags[boolTag] = cloneableTags[dateTag] =
+  cloneableTags[float32Tag] = cloneableTags[float64Tag] =
+  cloneableTags[int8Tag] = cloneableTags[int16Tag] =
+  cloneableTags[int32Tag] = cloneableTags[mapTag] =
+  cloneableTags[numberTag] = cloneableTags[objectTag] =
+  cloneableTags[regexpTag] = cloneableTags[setTag] =
+  cloneableTags[stringTag] = cloneableTags[symbolTag] =
+  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+  cloneableTags[errorTag] = cloneableTags[funcTag] =
+  cloneableTags[weakMapTag] = false;
+
+  /** Used to map latin-1 supplementary letters to basic latin letters. */
+  var deburredLetters = {
+    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+    '\xc7': 'C',  '\xe7': 'c',
+    '\xd0': 'D',  '\xf0': 'd',
+    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+    '\xcC': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+    '\xeC': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
+    '\xd1': 'N',  '\xf1': 'n',
+    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
+    '\xc6': 'Ae', '\xe6': 'ae',
+    '\xde': 'Th', '\xfe': 'th',
+    '\xdf': 'ss'
+  };
+
+  /** Used to map characters to HTML entities. */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;',
+    '`': '&#96;'
+  };
+
+  /** Used to map HTML entities to characters. */
+  var htmlUnescapes = {
+    '&amp;': '&',
+    '&lt;': '<',
+    '&gt;': '>',
+    '&quot;': '"',
+    '&#39;': "'",
+    '&#96;': '`'
+  };
+
+  /** Used to determine if values are of the language type `Object`. */
+  var objectTypes = {
+    'function': true,
+    'object': true
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals. */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Built-in method references without a dependency on `root`. */
+  var freeParseFloat = parseFloat,
+      freeParseInt = parseInt;
+
+  /** Detect free variable `exports`. */
+  var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType)
+    ? exports
+    : undefined;
+
+  /** Detect free variable `module`. */
+  var freeModule = (objectTypes[typeof module] && module && !module.nodeType)
+    ? module
+    : undefined;
+
+  /** Detect the popular CommonJS extension `module.exports`. */
+  var moduleExports = (freeModule && freeModule.exports === freeExports)
+    ? freeExports
+    : undefined;
+
+  /** Detect free variable `global` from Node.js. */
+  var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global);
+
+  /** Detect free variable `self`. */
+  var freeSelf = checkGlobal(objectTypes[typeof self] && self);
+
+  /** Detect free variable `window`. */
+  var freeWindow = checkGlobal(objectTypes[typeof window] && window);
+
+  /** Detect `this` as the global object. */
+  var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
+
+  /**
+   * Used as a reference to the global object.
+   *
+   * The `this` value is used if it's the global object to avoid Greasemonkey's
+   * restricted `window` object, otherwise the `window` object is used.
+   */
+  var root = freeGlobal ||
+    ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) ||
+      freeSelf || thisGlobal || Function('return this')();
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Adds the key-value `pair` to `map`.
+   *
+   * @private
+   * @param {Object} map The map to modify.
+   * @param {Array} pair The key-value pair to add.
+   * @returns {Object} Returns `map`.
+   */
+  function addMapEntry(map, pair) {
+    // Don't return `Map#set` because it doesn't return the map instance in IE 11.
+    map.set(pair[0], pair[1]);
+    return map;
+  }
+
+  /**
+   * Adds `value` to `set`.
+   *
+   * @private
+   * @param {Object} set The set to modify.
+   * @param {*} value The value to add.
+   * @returns {Object} Returns `set`.
+   */
+  function addSetEntry(set, value) {
+    set.add(value);
+    return set;
+  }
+
+  /**
+   * A faster alternative to `Function#apply`, this function invokes `func`
+   * with the `this` binding of `thisArg` and the arguments of `args`.
+   *
+   * @private
+   * @param {Function} func The function to invoke.
+   * @param {*} thisArg The `this` binding of `func`.
+   * @param {Array} args The arguments to invoke `func` with.
+   * @returns {*} Returns the result of `func`.
+   */
+  function apply(func, thisArg, args) {
+    var length = args.length;
+    switch (length) {
+      case 0: return func.call(thisArg);
+      case 1: return func.call(thisArg, args[0]);
+      case 2: return func.call(thisArg, args[0], args[1]);
+      case 3: return func.call(thisArg, args[0], args[1], args[2]);
+    }
+    return func.apply(thisArg, args);
+  }
+
+  /**
+   * A specialized version of `baseAggregator` for arrays.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} setter The function to set `accumulator` values.
+   * @param {Function} iteratee The iteratee to transform keys.
+   * @param {Object} accumulator The initial aggregated object.
+   * @returns {Function} Returns `accumulator`.
+   */
+  function arrayAggregator(array, setter, iteratee, accumulator) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var value = array[index];
+      setter(accumulator, value, iteratee(value), array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * Creates a new array concatenating `array` with `other`.
+   *
+   * @private
+   * @param {Array} array The first array to concatenate.
+   * @param {Array} other The second array to concatenate.
+   * @returns {Array} Returns the new concatenated array.
+   */
+  function arrayConcat(array, other) {
+    var index = -1,
+        length = array.length,
+        othIndex = -1,
+        othLength = other.length,
+        result = Array(length + othLength);
+
+    while (++index < length) {
+      result[index] = array[index];
+    }
+    while (++othIndex < othLength) {
+      result[index++] = other[othIndex];
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.forEach` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEach(array, iteratee) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (iteratee(array[index], index, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.forEachRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEachRight(array, iteratee) {
+    var length = array.length;
+
+    while (length--) {
+      if (iteratee(array[length], length, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.every` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if all elements pass the predicate check,
+   *  else `false`.
+   */
+  function arrayEvery(array, predicate) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (!predicate(array[index], index, array)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * A specialized version of `_.filter` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {Array} Returns the new filtered array.
+   */
+  function arrayFilter(array, predicate) {
+    var index = -1,
+        length = array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (predicate(value, index, array)) {
+        result[resIndex++] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.includes` for arrays without support for
+   * specifying an index to search from.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} target The value to search for.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludes(array, value) {
+    return !!array.length && baseIndexOf(array, value, 0) > -1;
+  }
+
+  /**
+   * This function is like `arrayIncludes` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} target The value to search for.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludesWith(array, value, comparator) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (comparator(value, array[index])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A specialized version of `_.map` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the new mapped array.
+   */
+  function arrayMap(array, iteratee) {
+    var index = -1,
+        length = array.length,
+        result = Array(length);
+
+    while (++index < length) {
+      result[index] = iteratee(array[index], index, array);
+    }
+    return result;
+  }
+
+  /**
+   * Appends the elements of `values` to `array`.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {Array} values The values to append.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayPush(array, values) {
+    var index = -1,
+        length = values.length,
+        offset = array.length;
+
+    while (++index < length) {
+      array[offset + index] = values[index];
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.reduce` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the first element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduce(array, iteratee, accumulator, initAccum) {
+    var index = -1,
+        length = array.length;
+
+    if (initAccum && length) {
+      accumulator = array[++index];
+    }
+    while (++index < length) {
+      accumulator = iteratee(accumulator, array[index], index, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.reduceRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the last element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+    var length = array.length;
+    if (initAccum && length) {
+      accumulator = array[--length];
+    }
+    while (length--) {
+      accumulator = iteratee(accumulator, array[length], length, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.some` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if any element passes the predicate check,
+   *  else `false`.
+   */
+  function arraySome(array, predicate) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (predicate(array[index], index, array)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * The base implementation of methods like `_.find` and `_.findKey`, without
+   * support for iteratee shorthands, which iterates over `collection` using
+   * `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to search.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @param {boolean} [retKey] Specify returning the key of the found element
+   *  instead of the element itself.
+   * @returns {*} Returns the found element or its key, else `undefined`.
+   */
+  function baseFind(collection, predicate, eachFunc, retKey) {
+    var result;
+    eachFunc(collection, function(value, key, collection) {
+      if (predicate(value, key, collection)) {
+        result = retKey ? key : value;
+        return false;
+      }
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.findIndex` and `_.findLastIndex` without
+   * support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseFindIndex(array, predicate, fromRight) {
+    var length = array.length,
+        index = fromRight ? length : -1;
+
+    while ((fromRight ? index-- : ++index < length)) {
+      if (predicate(array[index], index, array)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    if (value !== value) {
+      return indexOfNaN(array, fromIndex);
+    }
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * This function is like `baseIndexOf` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOfWith(array, value, fromIndex, comparator) {
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (comparator(array[index], value)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.mean` and `_.meanBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the mean.
+   */
+  function baseMean(array, iteratee) {
+    var length = array ? array.length : 0;
+    return length ? (baseSum(array, iteratee) / length) : NAN;
+  }
+
+  /**
+   * The base implementation of `_.reduce` and `_.reduceRight`, without support
+   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} accumulator The initial value.
+   * @param {boolean} initAccum Specify using the first or last element of
+   *  `collection` as the initial value.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the accumulated value.
+   */
+  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+    eachFunc(collection, function(value, index, collection) {
+      accumulator = initAccum
+        ? (initAccum = false, value)
+        : iteratee(accumulator, value, index, collection);
+    });
+    return accumulator;
+  }
+
+  /**
+   * The base implementation of `_.sortBy` which uses `comparer` to define the
+   * sort order of `array` and replaces criteria objects with their corresponding
+   * values.
+   *
+   * @private
+   * @param {Array} array The array to sort.
+   * @param {Function} comparer The function to define sort order.
+   * @returns {Array} Returns `array`.
+   */
+  function baseSortBy(array, comparer) {
+    var length = array.length;
+
+    array.sort(comparer);
+    while (length--) {
+      array[length] = array[length].value;
+    }
+    return array;
+  }
+
+  /**
+   * The base implementation of `_.sum` and `_.sumBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the sum.
+   */
+  function baseSum(array, iteratee) {
+    var result,
+        index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var current = iteratee(array[index]);
+      if (current !== undefined) {
+        result = result === undefined ? current : (result + current);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.times` without support for iteratee shorthands
+   * or max array length checks.
+   *
+   * @private
+   * @param {number} n The number of times to invoke `iteratee`.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the array of results.
+   */
+  function baseTimes(n, iteratee) {
+    var index = -1,
+        result = Array(n);
+
+    while (++index < n) {
+      result[index] = iteratee(index);
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+   * of key-value pairs for `object` corresponding to the property names of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the new array of key-value pairs.
+   */
+  function baseToPairs(object, props) {
+    return arrayMap(props, function(key) {
+      return [key, object[key]];
+    });
+  }
+
+  /**
+   * The base implementation of `_.unary` without support for storing wrapper metadata.
+   *
+   * @private
+   * @param {Function} func The function to cap arguments for.
+   * @returns {Function} Returns the new function.
+   */
+  function baseUnary(func) {
+    return function(value) {
+      return func(value);
+    };
+  }
+
+  /**
+   * The base implementation of `_.values` and `_.valuesIn` which creates an
+   * array of `object` property values corresponding to the property names
+   * of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the array of property values.
+   */
+  function baseValues(object, props) {
+    return arrayMap(props, function(key) {
+      return object[key];
+    });
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the first unmatched string symbol.
+   */
+  function charsStartIndex(strSymbols, chrSymbols) {
+    var index = -1,
+        length = strSymbols.length;
+
+    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the last unmatched string symbol.
+   */
+  function charsEndIndex(strSymbols, chrSymbols) {
+    var index = strSymbols.length;
+
+    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Checks if `value` is a global object.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {null|Object} Returns `value` if it's a global object, else `null`.
+   */
+  function checkGlobal(value) {
+    return (value && value.Object === Object) ? value : null;
+  }
+
+  /**
+   * Gets the number of `placeholder` occurrences in `array`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} placeholder The placeholder to search for.
+   * @returns {number} Returns the placeholder count.
+   */
+  function countHolders(array, placeholder) {
+    var length = array.length,
+        result = 0;
+
+    while (length--) {
+      if (array[length] === placeholder) {
+        result++;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
+   *
+   * @private
+   * @param {string} letter The matched letter to deburr.
+   * @returns {string} Returns the deburred letter.
+   */
+  function deburrLetter(letter) {
+    return deburredLetters[letter];
+  }
+
+  /**
+   * Used by `_.escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeHtmlChar(chr) {
+    return htmlEscapes[chr];
+  }
+
+  /**
+   * Used by `_.template` to escape characters for inclusion in compiled string literals.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(chr) {
+    return '\\' + stringEscapes[chr];
+  }
+
+  /**
+   * Gets the index at which the first occurrence of `NaN` is found in `array`.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {number} fromIndex The index to search from.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched `NaN`, else `-1`.
+   */
+  function indexOfNaN(array, fromIndex, fromRight) {
+    var length = array.length,
+        index = fromIndex + (fromRight ? 0 : -1);
+
+    while ((fromRight ? index-- : ++index < length)) {
+      var other = array[index];
+      if (other !== other) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Checks if `value` is a host object in IE < 9.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+   */
+  function isHostObject(value) {
+    // Many host objects are `Object` objects that can coerce to strings
+    // despite having improperly defined `toString` methods.
+    var result = false;
+    if (value != null && typeof value.toString != 'function') {
+      try {
+        result = !!(value + '');
+      } catch (e) {}
+    }
+    return result;
+  }
+
+  /**
+   * Converts `iterator` to an array.
+   *
+   * @private
+   * @param {Object} iterator The iterator to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function iteratorToArray(iterator) {
+    var data,
+        result = [];
+
+    while (!(data = iterator.next()).done) {
+      result.push(data.value);
+    }
+    return result;
+  }
+
+  /**
+   * Converts `map` to an array.
+   *
+   * @private
+   * @param {Object} map The map to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function mapToArray(map) {
+    var index = -1,
+        result = Array(map.size);
+
+    map.forEach(function(value, key) {
+      result[++index] = [key, value];
+    });
+    return result;
+  }
+
+  /**
+   * Replaces all `placeholder` elements in `array` with an internal placeholder
+   * and returns an array of their indexes.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {*} placeholder The placeholder to replace.
+   * @returns {Array} Returns the new array of placeholder indexes.
+   */
+  function replaceHolders(array, placeholder) {
+    var index = -1,
+        length = array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (value === placeholder || value === PLACEHOLDER) {
+        array[index] = PLACEHOLDER;
+        result[resIndex++] = index;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Converts `set` to an array.
+   *
+   * @private
+   * @param {Object} set The set to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function setToArray(set) {
+    var index = -1,
+        result = Array(set.size);
+
+    set.forEach(function(value) {
+      result[++index] = value;
+    });
+    return result;
+  }
+
+  /**
+   * Gets the number of symbols in `string`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {number} Returns the string size.
+   */
+  function stringSize(string) {
+    if (!(string && reHasComplexSymbol.test(string))) {
+      return string.length;
+    }
+    var result = reComplexSymbol.lastIndex = 0;
+    while (reComplexSymbol.test(string)) {
+      result++;
+    }
+    return result;
+  }
+
+  /**
+   * Converts `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function stringToArray(string) {
+    return string.match(reComplexSymbol);
+  }
+
+  /**
+   * Used by `_.unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {string} chr The matched character to unescape.
+   * @returns {string} Returns the unescaped character.
+   */
+  function unescapeHtmlChar(chr) {
+    return htmlUnescapes[chr];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new pristine `lodash` function using the `context` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 1.1.0
+   * @category Util
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns a new `lodash` function.
+   * @example
+   *
+   * _.mixin({ 'foo': _.constant('foo') });
+   *
+   * var lodash = _.runInContext();
+   * lodash.mixin({ 'bar': lodash.constant('bar') });
+   *
+   * _.isFunction(_.foo);
+   * // => true
+   * _.isFunction(_.bar);
+   * // => false
+   *
+   * lodash.isFunction(lodash.foo);
+   * // => false
+   * lodash.isFunction(lodash.bar);
+   * // => true
+   *
+   * // Use `context` to mock `Date#getTime` use in `_.now`.
+   * var mock = _.runInContext({
+   *   'Date': function() {
+   *     return { 'getTime': getTimeMock };
+   *   }
+   * });
+   *
+   * // Create a suped-up `defer` in Node.js.
+   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+   */
+  function runInContext(context) {
+    context = context ? _.defaults({}, context, _.pick(root, contextProps)) : root;
+
+    /** Built-in constructor references. */
+    var Date = context.Date,
+        Error = context.Error,
+        Math = context.Math,
+        RegExp = context.RegExp,
+        TypeError = context.TypeError;
+
+    /** Used for built-in method references. */
+    var arrayProto = context.Array.prototype,
+        objectProto = context.Object.prototype,
+        stringProto = context.String.prototype;
+
+    /** Used to resolve the decompiled source of functions. */
+    var funcToString = context.Function.prototype.toString;
+
+    /** Used to check objects for own properties. */
+    var hasOwnProperty = objectProto.hasOwnProperty;
+
+    /** Used to generate unique IDs. */
+    var idCounter = 0;
+
+    /** Used to infer the `Object` constructor. */
+    var objectCtorString = funcToString.call(Object);
+
+    /**
+     * Used to resolve the
+     * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+     * of values.
+     */
+    var objectToString = objectProto.toString;
+
+    /** Used to restore the original `_` reference in `_.noConflict`. */
+    var oldDash = root._;
+
+    /** Used to detect if a method is native. */
+    var reIsNative = RegExp('^' +
+      funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+    );
+
+    /** Built-in value references. */
+    var Buffer = moduleExports ? context.Buffer : undefined,
+        Reflect = context.Reflect,
+        Symbol = context.Symbol,
+        Uint8Array = context.Uint8Array,
+        clearTimeout = context.clearTimeout,
+        enumerate = Reflect ? Reflect.enumerate : undefined,
+        getOwnPropertySymbols = Object.getOwnPropertySymbols,
+        iteratorSymbol = typeof (iteratorSymbol = Symbol && Symbol.iterator) == 'symbol' ? iteratorSymbol : undefined,
+        objectCreate = Object.create,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        setTimeout = context.setTimeout,
+        splice = arrayProto.splice;
+
+    /* Built-in method references for those with the same name as other `lodash` methods. */
+    var nativeCeil = Math.ceil,
+        nativeFloor = Math.floor,
+        nativeGetPrototype = Object.getPrototypeOf,
+        nativeIsFinite = context.isFinite,
+        nativeJoin = arrayProto.join,
+        nativeKeys = Object.keys,
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random,
+        nativeReplace = stringProto.replace,
+        nativeReverse = arrayProto.reverse,
+        nativeSplit = stringProto.split;
+
+    /* Built-in method references that are verified to be native. */
+    var DataView = getNative(context, 'DataView'),
+        Map = getNative(context, 'Map'),
+        Promise = getNative(context, 'Promise'),
+        Set = getNative(context, 'Set'),
+        WeakMap = getNative(context, 'WeakMap'),
+        nativeCreate = getNative(Object, 'create');
+
+    /** Used to store function metadata. */
+    var metaMap = WeakMap && new WeakMap;
+
+    /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */
+    var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf');
+
+    /** Used to lookup unminified function names. */
+    var realNames = {};
+
+    /** Used to detect maps, sets, and weakmaps. */
+    var dataViewCtorString = toSource(DataView),
+        mapCtorString = toSource(Map),
+        promiseCtorString = toSource(Promise),
+        setCtorString = toSource(Set),
+        weakMapCtorString = toSource(WeakMap);
+
+    /** Used to convert symbols to primitives and strings. */
+    var symbolProto = Symbol ? Symbol.prototype : undefined,
+        symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
+        symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps `value` to enable implicit method
+     * chain sequences. Methods that operate on and return arrays, collections,
+     * and functions can be chained together. Methods that retrieve a single value
+     * or may return a primitive value will automatically end the chain sequence
+     * and return the unwrapped value. Otherwise, the value must be unwrapped
+     * with `_#value`.
+     *
+     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+     * enabled using `_.chain`.
+     *
+     * The execution of chained methods is lazy, that is, it's deferred until
+     * `_#value` is implicitly or explicitly called.
+     *
+     * Lazy evaluation allows several methods to support shortcut fusion.
+     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+     * the creation of intermediate arrays and can greatly reduce the number of
+     * iteratee executions. Sections of a chain sequence qualify for shortcut
+     * fusion if the section is applied to an array of at least `200` elements
+     * and any iteratees accept only one argument. The heuristic for whether a
+     * section qualifies for shortcut fusion is subject to change.
+     *
+     * Chaining is supported in custom builds as long as the `_#value` method is
+     * directly or indirectly included in the build.
+     *
+     * In addition to lodash methods, wrappers have `Array` and `String` methods.
+     *
+     * The wrapper `Array` methods are:
+     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+     *
+     * The wrapper `String` methods are:
+     * `replace` and `split`
+     *
+     * The wrapper methods that support shortcut fusion are:
+     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+     *
+     * The chainable wrapper methods are:
+     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+     * `zipObject`, `zipObjectDeep`, and `zipWith`
+     *
+     * The wrapper methods that are **not** chainable by default are:
+     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `deburr`, `divide`, `each`,
+     * `eachRight`, `endsWith`, `eq`, `escape`, `escapeRegExp`, `every`, `find`,
+     * `findIndex`, `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `first`,
+     * `floor`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`,
+     * `forOwnRight`, `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`,
+     * `includes`, `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`,
+     * `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, `isBuffer`,
+     * `isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, `isError`,
+     * `isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, `isMatch`,
+     * `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`,
+     * `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`,
+     * `isSet`, `isString`, `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`,
+     * `join`, `kebabCase`, `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`,
+     * `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, `min`, `minBy`, `multiply`,
+     * `noConflict`, `noop`, `now`, `nth`, `pad`, `padEnd`, `padStart`, `parseInt`,
+     * `pop`, `random`, `reduce`, `reduceRight`, `repeat`, `result`, `round`,
+     * `runInContext`, `sample`, `shift`, `size`, `snakeCase`, `some`, `sortedIndex`,
+     * `sortedIndexBy`, `sortedLastIndex`, `sortedLastIndexBy`, `startCase`,
+     * `startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toInteger`,
+     * `toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, `toString`,
+     * `toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`,
+     * `uniqueId`, `upperCase`, `upperFirst`, `value`, and `words`
+     *
+     * @name _
+     * @constructor
+     * @category Seq
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // Returns an unwrapped value.
+     * wrapped.reduce(_.add);
+     * // => 6
+     *
+     * // Returns a wrapped value.
+     * var squares = wrapped.map(square);
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+        if (value instanceof LodashWrapper) {
+          return value;
+        }
+        if (hasOwnProperty.call(value, '__wrapped__')) {
+          return wrapperClone(value);
+        }
+      }
+      return new LodashWrapper(value);
+    }
+
+    /**
+     * The function whose prototype chain sequence wrappers inherit from.
+     *
+     * @private
+     */
+    function baseLodash() {
+      // No operation performed.
+    }
+
+    /**
+     * The base constructor for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap.
+     * @param {boolean} [chainAll] Enable explicit method chain sequences.
+     */
+    function LodashWrapper(value, chainAll) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__chain__ = !!chainAll;
+      this.__index__ = 0;
+      this.__values__ = undefined;
+    }
+
+    /**
+     * By default, the template delimiters used by lodash are like 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': reEscape,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'evaluate': reEvaluate,
+
+      /**
+       * 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
+      }
+    };
+
+    // Ensure wrappers are instances of `baseLodash`.
+    lodash.prototype = baseLodash.prototype;
+    lodash.prototype.constructor = lodash;
+
+    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+    LodashWrapper.prototype.constructor = LodashWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+     *
+     * @private
+     * @constructor
+     * @param {*} value The value to wrap.
+     */
+    function LazyWrapper(value) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__dir__ = 1;
+      this.__filtered__ = false;
+      this.__iteratees__ = [];
+      this.__takeCount__ = MAX_ARRAY_LENGTH;
+      this.__views__ = [];
+    }
+
+    /**
+     * Creates a clone of the lazy wrapper object.
+     *
+     * @private
+     * @name clone
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the cloned `LazyWrapper` object.
+     */
+    function lazyClone() {
+      var result = new LazyWrapper(this.__wrapped__);
+      result.__actions__ = copyArray(this.__actions__);
+      result.__dir__ = this.__dir__;
+      result.__filtered__ = this.__filtered__;
+      result.__iteratees__ = copyArray(this.__iteratees__);
+      result.__takeCount__ = this.__takeCount__;
+      result.__views__ = copyArray(this.__views__);
+      return result;
+    }
+
+    /**
+     * Reverses the direction of lazy iteration.
+     *
+     * @private
+     * @name reverse
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the new reversed `LazyWrapper` object.
+     */
+    function lazyReverse() {
+      if (this.__filtered__) {
+        var result = new LazyWrapper(this);
+        result.__dir__ = -1;
+        result.__filtered__ = true;
+      } else {
+        result = this.clone();
+        result.__dir__ *= -1;
+      }
+      return result;
+    }
+
+    /**
+     * Extracts the unwrapped value from its lazy wrapper.
+     *
+     * @private
+     * @name value
+     * @memberOf LazyWrapper
+     * @returns {*} Returns the unwrapped value.
+     */
+    function lazyValue() {
+      var array = this.__wrapped__.value(),
+          dir = this.__dir__,
+          isArr = isArray(array),
+          isRight = dir < 0,
+          arrLength = isArr ? array.length : 0,
+          view = getView(0, arrLength, this.__views__),
+          start = view.start,
+          end = view.end,
+          length = end - start,
+          index = isRight ? end : (start - 1),
+          iteratees = this.__iteratees__,
+          iterLength = iteratees.length,
+          resIndex = 0,
+          takeCount = nativeMin(length, this.__takeCount__);
+
+      if (!isArr || arrLength < LARGE_ARRAY_SIZE ||
+          (arrLength == length && takeCount == length)) {
+        return baseWrapperValue(array, this.__actions__);
+      }
+      var result = [];
+
+      outer:
+      while (length-- && resIndex < takeCount) {
+        index += dir;
+
+        var iterIndex = -1,
+            value = array[index];
+
+        while (++iterIndex < iterLength) {
+          var data = iteratees[iterIndex],
+              iteratee = data.iteratee,
+              type = data.type,
+              computed = iteratee(value);
+
+          if (type == LAZY_MAP_FLAG) {
+            value = computed;
+          } else if (!computed) {
+            if (type == LAZY_FILTER_FLAG) {
+              continue outer;
+            } else {
+              break outer;
+            }
+          }
+        }
+        result[resIndex++] = value;
+      }
+      return result;
+    }
+
+    // Ensure `LazyWrapper` is an instance of `baseLodash`.
+    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+    LazyWrapper.prototype.constructor = LazyWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a hash object.
+     *
+     * @private
+     * @constructor
+     * @returns {Object} Returns the new hash object.
+     */
+    function Hash() {}
+
+    /**
+     * Removes `key` and its value from the hash.
+     *
+     * @private
+     * @param {Object} hash The hash to modify.
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function hashDelete(hash, key) {
+      return hashHas(hash, key) && delete hash[key];
+    }
+
+    /**
+     * Gets the hash value for `key`.
+     *
+     * @private
+     * @param {Object} hash The hash to query.
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function hashGet(hash, key) {
+      if (nativeCreate) {
+        var result = hash[key];
+        return result === HASH_UNDEFINED ? undefined : result;
+      }
+      return hasOwnProperty.call(hash, key) ? hash[key] : undefined;
+    }
+
+    /**
+     * Checks if a hash value for `key` exists.
+     *
+     * @private
+     * @param {Object} hash The hash to query.
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function hashHas(hash, key) {
+      return nativeCreate ? hash[key] !== undefined : hasOwnProperty.call(hash, key);
+    }
+
+    /**
+     * Sets the hash `key` to `value`.
+     *
+     * @private
+     * @param {Object} hash The hash to modify.
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     */
+    function hashSet(hash, key, value) {
+      hash[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+    }
+
+    // Avoid inheriting from `Object.prototype` when possible.
+    Hash.prototype = nativeCreate ? nativeCreate(null) : objectProto;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a map cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function MapCache(values) {
+      var index = -1,
+          length = values ? values.length : 0;
+
+      this.clear();
+      while (++index < length) {
+        var entry = values[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the map.
+     *
+     * @private
+     * @name clear
+     * @memberOf MapCache
+     */
+    function mapClear() {
+      this.__data__ = {
+        'hash': new Hash,
+        'map': Map ? new Map : [],
+        'string': new Hash
+      };
+    }
+
+    /**
+     * Removes `key` and its value from the map.
+     *
+     * @private
+     * @name delete
+     * @memberOf MapCache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function mapDelete(key) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        return hashDelete(typeof key == 'string' ? data.string : data.hash, key);
+      }
+      return Map ? data.map['delete'](key) : assocDelete(data.map, key);
+    }
+
+    /**
+     * Gets the map value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf MapCache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function mapGet(key) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        return hashGet(typeof key == 'string' ? data.string : data.hash, key);
+      }
+      return Map ? data.map.get(key) : assocGet(data.map, key);
+    }
+
+    /**
+     * Checks if a map value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf MapCache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function mapHas(key) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        return hashHas(typeof key == 'string' ? data.string : data.hash, key);
+      }
+      return Map ? data.map.has(key) : assocHas(data.map, key);
+    }
+
+    /**
+     * Sets the map `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf MapCache
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the map cache instance.
+     */
+    function mapSet(key, value) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        hashSet(typeof key == 'string' ? data.string : data.hash, key, value);
+      } else if (Map) {
+        data.map.set(key, value);
+      } else {
+        assocSet(data.map, key, value);
+      }
+      return this;
+    }
+
+    // Add methods to `MapCache`.
+    MapCache.prototype.clear = mapClear;
+    MapCache.prototype['delete'] = mapDelete;
+    MapCache.prototype.get = mapGet;
+    MapCache.prototype.has = mapHas;
+    MapCache.prototype.set = mapSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     *
+     * Creates a set cache object to store unique values.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function SetCache(values) {
+      var index = -1,
+          length = values ? values.length : 0;
+
+      this.__data__ = new MapCache;
+      while (++index < length) {
+        this.push(values[index]);
+      }
+    }
+
+    /**
+     * Checks if `value` is in `cache`.
+     *
+     * @private
+     * @param {Object} cache The set cache to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns `true` if `value` is found, else `false`.
+     */
+    function cacheHas(cache, value) {
+      var map = cache.__data__;
+      if (isKeyable(value)) {
+        var data = map.__data__,
+            hash = typeof value == 'string' ? data.string : data.hash;
+
+        return hash[value] === HASH_UNDEFINED;
+      }
+      return map.has(value);
+    }
+
+    /**
+     * Adds `value` to the set cache.
+     *
+     * @private
+     * @name push
+     * @memberOf SetCache
+     * @param {*} value The value to cache.
+     */
+    function cachePush(value) {
+      var map = this.__data__;
+      if (isKeyable(value)) {
+        var data = map.__data__,
+            hash = typeof value == 'string' ? data.string : data.hash;
+
+        hash[value] = HASH_UNDEFINED;
+      }
+      else {
+        map.set(value, HASH_UNDEFINED);
+      }
+    }
+
+    // Add methods to `SetCache`.
+    SetCache.prototype.push = cachePush;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a stack cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function Stack(values) {
+      var index = -1,
+          length = values ? values.length : 0;
+
+      this.clear();
+      while (++index < length) {
+        var entry = values[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the stack.
+     *
+     * @private
+     * @name clear
+     * @memberOf Stack
+     */
+    function stackClear() {
+      this.__data__ = { 'array': [], 'map': null };
+    }
+
+    /**
+     * Removes `key` and its value from the stack.
+     *
+     * @private
+     * @name delete
+     * @memberOf Stack
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function stackDelete(key) {
+      var data = this.__data__,
+          array = data.array;
+
+      return array ? assocDelete(array, key) : data.map['delete'](key);
+    }
+
+    /**
+     * Gets the stack value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf Stack
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function stackGet(key) {
+      var data = this.__data__,
+          array = data.array;
+
+      return array ? assocGet(array, key) : data.map.get(key);
+    }
+
+    /**
+     * Checks if a stack value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf Stack
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function stackHas(key) {
+      var data = this.__data__,
+          array = data.array;
+
+      return array ? assocHas(array, key) : data.map.has(key);
+    }
+
+    /**
+     * Sets the stack `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf Stack
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the stack cache instance.
+     */
+    function stackSet(key, value) {
+      var data = this.__data__,
+          array = data.array;
+
+      if (array) {
+        if (array.length < (LARGE_ARRAY_SIZE - 1)) {
+          assocSet(array, key, value);
+        } else {
+          data.array = null;
+          data.map = new MapCache(array);
+        }
+      }
+      var map = data.map;
+      if (map) {
+        map.set(key, value);
+      }
+      return this;
+    }
+
+    // Add methods to `Stack`.
+    Stack.prototype.clear = stackClear;
+    Stack.prototype['delete'] = stackDelete;
+    Stack.prototype.get = stackGet;
+    Stack.prototype.has = stackHas;
+    Stack.prototype.set = stackSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Removes `key` and its value from the associative array.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function assocDelete(array, key) {
+      var index = assocIndexOf(array, key);
+      if (index < 0) {
+        return false;
+      }
+      var lastIndex = array.length - 1;
+      if (index == lastIndex) {
+        array.pop();
+      } else {
+        splice.call(array, index, 1);
+      }
+      return true;
+    }
+
+    /**
+     * Gets the associative array value for `key`.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function assocGet(array, key) {
+      var index = assocIndexOf(array, key);
+      return index < 0 ? undefined : array[index][1];
+    }
+
+    /**
+     * Checks if an associative array value for `key` exists.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function assocHas(array, key) {
+      return assocIndexOf(array, key) > -1;
+    }
+
+    /**
+     * Gets the index at which the `key` is found in `array` of key-value pairs.
+     *
+     * @private
+     * @param {Array} array The array to search.
+     * @param {*} key The key to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     */
+    function assocIndexOf(array, key) {
+      var length = array.length;
+      while (length--) {
+        if (eq(array[length][0], key)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Sets the associative array `key` to `value`.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     */
+    function assocSet(array, key, value) {
+      var index = assocIndexOf(array, key);
+      if (index < 0) {
+        array.push([key, value]);
+      } else {
+        array[index][1] = value;
+      }
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Used by `_.defaults` to customize its `_.assignIn` use.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to assign.
+     * @param {Object} object The parent object of `objValue`.
+     * @returns {*} Returns the value to assign.
+     */
+    function assignInDefaults(objValue, srcValue, key, object) {
+      if (objValue === undefined ||
+          (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+        return srcValue;
+      }
+      return objValue;
+    }
+
+    /**
+     * This function is like `assignValue` except that it doesn't assign
+     * `undefined` values.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignMergeValue(object, key, value) {
+      if ((value !== undefined && !eq(object[key], value)) ||
+          (typeof key == 'number' && value === undefined && !(key in object))) {
+        object[key] = value;
+      }
+    }
+
+    /**
+     * Assigns `value` to `key` of `object` if the existing value is not equivalent
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignValue(object, key, value) {
+      var objValue = object[key];
+      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+          (value === undefined && !(key in object))) {
+        object[key] = value;
+      }
+    }
+
+    /**
+     * Aggregates elements of `collection` on `accumulator` with keys transformed
+     * by `iteratee` and values set by `setter`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform keys.
+     * @param {Object} accumulator The initial aggregated object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseAggregator(collection, setter, iteratee, accumulator) {
+      baseEach(collection, function(value, key, collection) {
+        setter(accumulator, value, iteratee(value), collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.assign` without support for multiple sources
+     * or `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssign(object, source) {
+      return object && copyObject(source, keys(source), object);
+    }
+
+    /**
+     * The base implementation of `_.at` without support for individual paths.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {string[]} paths The property paths of elements to pick.
+     * @returns {Array} Returns the new array of picked elements.
+     */
+    function baseAt(object, paths) {
+      var index = -1,
+          isNil = object == null,
+          length = paths.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = isNil ? undefined : get(object, paths[index]);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.clamp` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     */
+    function baseClamp(number, lower, upper) {
+      if (number === number) {
+        if (upper !== undefined) {
+          number = number <= upper ? number : upper;
+        }
+        if (lower !== undefined) {
+          number = number >= lower ? number : lower;
+        }
+      }
+      return number;
+    }
+
+    /**
+     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+     * traversed objects.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @param {boolean} [isFull] Specify a clone including symbols.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @param {string} [key] The key of `value`.
+     * @param {Object} [object] The parent object of `value`.
+     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, isDeep, isFull, customizer, key, object, stack) {
+      var result;
+      if (customizer) {
+        result = object ? customizer(value, key, object, stack) : customizer(value);
+      }
+      if (result !== undefined) {
+        return result;
+      }
+      if (!isObject(value)) {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isArr) {
+        result = initCloneArray(value);
+        if (!isDeep) {
+          return copyArray(value, result);
+        }
+      } else {
+        var tag = getTag(value),
+            isFunc = tag == funcTag || tag == genTag;
+
+        if (isBuffer(value)) {
+          return cloneBuffer(value, isDeep);
+        }
+        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+          if (isHostObject(value)) {
+            return object ? value : {};
+          }
+          result = initCloneObject(isFunc ? {} : value);
+          if (!isDeep) {
+            return copySymbols(value, baseAssign(result, value));
+          }
+        } else {
+          if (!cloneableTags[tag]) {
+            return object ? value : {};
+          }
+          result = initCloneByTag(value, tag, baseClone, isDeep);
+        }
+      }
+      // Check for circular references and return its corresponding clone.
+      stack || (stack = new Stack);
+      var stacked = stack.get(value);
+      if (stacked) {
+        return stacked;
+      }
+      stack.set(value, result);
+
+      if (!isArr) {
+        var props = isFull ? getAllKeys(value) : keys(value);
+      }
+      // Recursively populate clone (susceptible to call stack limits).
+      arrayEach(props || value, function(subValue, key) {
+        if (props) {
+          key = subValue;
+          subValue = value[key];
+        }
+        assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.conforms` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new function.
+     */
+    function baseConforms(source) {
+      var props = keys(source),
+          length = props.length;
+
+      return function(object) {
+        if (object == null) {
+          return !length;
+        }
+        var index = length;
+        while (index--) {
+          var key = props[index],
+              predicate = source[key],
+              value = object[key];
+
+          if ((value === undefined &&
+              !(key in Object(object))) || !predicate(value)) {
+            return false;
+          }
+        }
+        return true;
+      };
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} prototype The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    function baseCreate(proto) {
+      return isObject(proto) ? objectCreate(proto) : {};
+    }
+
+    /**
+     * The base implementation of `_.delay` and `_.defer` which accepts an array
+     * of `func` arguments.
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {Object} args The arguments to provide to `func`.
+     * @returns {number} Returns the timer id.
+     */
+    function baseDelay(func, wait, args) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * The base implementation of methods like `_.difference` without support
+     * for excluding multiple arrays or iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Array} values The values to exclude.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     */
+    function baseDifference(array, values, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          isCommon = true,
+          length = array.length,
+          result = [],
+          valuesLength = values.length;
+
+      if (!length) {
+        return result;
+      }
+      if (iteratee) {
+        values = arrayMap(values, baseUnary(iteratee));
+      }
+      if (comparator) {
+        includes = arrayIncludesWith;
+        isCommon = false;
+      }
+      else if (values.length >= LARGE_ARRAY_SIZE) {
+        includes = cacheHas;
+        isCommon = false;
+        values = new SetCache(values);
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var valuesIndex = valuesLength;
+          while (valuesIndex--) {
+            if (values[valuesIndex] === computed) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+        else if (!includes(values, computed, comparator)) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.forEach` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEach = createBaseEach(baseForOwn);
+
+    /**
+     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+    /**
+     * The base implementation of `_.every` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`
+     */
+    function baseEvery(collection, predicate) {
+      var result = true;
+      baseEach(collection, function(value, index, collection) {
+        result = !!predicate(value, index, collection);
+        return result;
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of methods like `_.max` and `_.min` which accepts a
+     * `comparator` to determine the extremum value.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The iteratee invoked per iteration.
+     * @param {Function} comparator The comparator used to compare values.
+     * @returns {*} Returns the extremum value.
+     */
+    function baseExtremum(array, iteratee, comparator) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        var value = array[index],
+            current = iteratee(value);
+
+        if (current != null && (computed === undefined
+              ? (current === current && !isSymbol(current))
+              : comparator(current, computed)
+            )) {
+          var computed = current,
+              result = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.fill` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     */
+    function baseFill(array, value, start, end) {
+      var length = array.length;
+
+      start = toInteger(start);
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = (end === undefined || end > length) ? length : toInteger(end);
+      if (end < 0) {
+        end += length;
+      }
+      end = start > end ? 0 : toLength(end);
+      while (start < end) {
+        array[start++] = value;
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.filter` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     */
+    function baseFilter(collection, predicate) {
+      var result = [];
+      baseEach(collection, function(value, index, collection) {
+        if (predicate(value, index, collection)) {
+          result.push(value);
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` with support for restricting flattening.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {number} depth The maximum recursion depth.
+     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+     * @param {Array} [result=[]] The initial result value.
+     * @returns {Array} Returns the new flattened array.
+     */
+    function baseFlatten(array, depth, predicate, isStrict, result) {
+      var index = -1,
+          length = array.length;
+
+      predicate || (predicate = isFlattenable);
+      result || (result = []);
+
+      while (++index < length) {
+        var value = array[index];
+        if (depth > 0 && predicate(value)) {
+          if (depth > 1) {
+            // Recursively flatten arrays (susceptible to call stack limits).
+            baseFlatten(value, depth - 1, predicate, isStrict, result);
+          } else {
+            arrayPush(result, value);
+          }
+        } else if (!isStrict) {
+          result[result.length] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `baseForOwn` which iterates over `object`
+     * properties returned by `keysFunc` and invokes `iteratee` for each property.
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseFor = createBaseFor();
+
+    /**
+     * This function is like `baseFor` except that it iterates over properties
+     * in the opposite order.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseForRight = createBaseFor(true);
+
+    /**
+     * The base implementation of `_.forOwn` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwn(object, iteratee) {
+      return object && baseFor(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwnRight(object, iteratee) {
+      return object && baseForRight(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.functions` which creates an array of
+     * `object` function property names filtered from `props`.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Array} props The property names to filter.
+     * @returns {Array} Returns the new array of filtered property names.
+     */
+    function baseFunctions(object, props) {
+      return arrayFilter(props, function(key) {
+        return isFunction(object[key]);
+      });
+    }
+
+    /**
+     * The base implementation of `_.get` without support for default values.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseGet(object, path) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var index = 0,
+          length = path.length;
+
+      while (object != null && index < length) {
+        object = object[toKey(path[index++])];
+      }
+      return (index && index == length) ? object : undefined;
+    }
+
+    /**
+     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
+     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @param {Function} symbolsFunc The function to get the symbols of `object`.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
+      var result = keysFunc(object);
+      return isArray(object)
+        ? result
+        : arrayPush(result, symbolsFunc(object));
+    }
+
+    /**
+     * The base implementation of `_.gt` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     */
+    function baseGt(value, other) {
+      return value > other;
+    }
+
+    /**
+     * The base implementation of `_.has` without support for deep paths.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHas(object, key) {
+      // Avoid a bug in IE 10-11 where objects with a [[Prototype]] of `null`,
+      // that are composed entirely of index properties, return `false` for
+      // `hasOwnProperty` checks of them.
+      return hasOwnProperty.call(object, key) ||
+        (typeof object == 'object' && key in object && getPrototype(object) === null);
+    }
+
+    /**
+     * The base implementation of `_.hasIn` without support for deep paths.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHasIn(object, key) {
+      return key in Object(object);
+    }
+
+    /**
+     * The base implementation of `_.inRange` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {number} number The number to check.
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     */
+    function baseInRange(number, start, end) {
+      return number >= nativeMin(start, end) && number < nativeMax(start, end);
+    }
+
+    /**
+     * The base implementation of methods like `_.intersection`, without support
+     * for iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of shared values.
+     */
+    function baseIntersection(arrays, iteratee, comparator) {
+      var includes = comparator ? arrayIncludesWith : arrayIncludes,
+          length = arrays[0].length,
+          othLength = arrays.length,
+          othIndex = othLength,
+          caches = Array(othLength),
+          maxLength = Infinity,
+          result = [];
+
+      while (othIndex--) {
+        var array = arrays[othIndex];
+        if (othIndex && iteratee) {
+          array = arrayMap(array, baseUnary(iteratee));
+        }
+        maxLength = nativeMin(array.length, maxLength);
+        caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
+          ? new SetCache(othIndex && array)
+          : undefined;
+      }
+      array = arrays[0];
+
+      var index = -1,
+          seen = caches[0];
+
+      outer:
+      while (++index < length && result.length < maxLength) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (!(seen
+              ? cacheHas(seen, computed)
+              : includes(result, computed, comparator)
+            )) {
+          othIndex = othLength;
+          while (--othIndex) {
+            var cache = caches[othIndex];
+            if (!(cache
+                  ? cacheHas(cache, computed)
+                  : includes(arrays[othIndex], computed, comparator))
+                ) {
+              continue outer;
+            }
+          }
+          if (seen) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.invert` and `_.invertBy` which inverts
+     * `object` with values transformed by `iteratee` and set by `setter`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform values.
+     * @param {Object} accumulator The initial inverted object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseInverter(object, setter, iteratee, accumulator) {
+      baseForOwn(object, function(value, key, object) {
+        setter(accumulator, iteratee(value), key, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.invoke` without support for individual
+     * method arguments.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {Array} args The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     */
+    function baseInvoke(object, path, args) {
+      if (!isKey(path, object)) {
+        path = castPath(path);
+        object = parent(object, path);
+        path = last(path);
+      }
+      var func = object == null ? object : object[toKey(path)];
+      return func == null ? undefined : apply(func, object, args);
+    }
+
+    /**
+     * The base implementation of `_.isEqual` which supports partial comparisons
+     * and tracks traversed objects.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @param {boolean} [bitmask] The bitmask of comparison flags.
+     *  The bitmask may be composed of the following flags:
+     *     1 - Unordered comparison
+     *     2 - Partial comparison
+     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(value, other, customizer, bitmask, stack) {
+      if (value === other) {
+        return true;
+      }
+      if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+        return value !== value && other !== other;
+      }
+      return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
+    }
+
+    /**
+     * A specialized version of `baseIsEqual` for arrays and objects which performs
+     * deep comparisons and tracks traversed objects enabling objects with circular
+     * references to be compared.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
+      var objIsArr = isArray(object),
+          othIsArr = isArray(other),
+          objTag = arrayTag,
+          othTag = arrayTag;
+
+      if (!objIsArr) {
+        objTag = getTag(object);
+        objTag = objTag == argsTag ? objectTag : objTag;
+      }
+      if (!othIsArr) {
+        othTag = getTag(other);
+        othTag = othTag == argsTag ? objectTag : othTag;
+      }
+      var objIsObj = objTag == objectTag && !isHostObject(object),
+          othIsObj = othTag == objectTag && !isHostObject(other),
+          isSameTag = objTag == othTag;
+
+      if (isSameTag && !objIsObj) {
+        stack || (stack = new Stack);
+        return (objIsArr || isTypedArray(object))
+          ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)
+          : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);
+      }
+      if (!(bitmask & PARTIAL_COMPARE_FLAG)) {
+        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+        if (objIsWrapped || othIsWrapped) {
+          var objUnwrapped = objIsWrapped ? object.value() : object,
+              othUnwrapped = othIsWrapped ? other.value() : other;
+
+          stack || (stack = new Stack);
+          return equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack);
+        }
+      }
+      if (!isSameTag) {
+        return false;
+      }
+      stack || (stack = new Stack);
+      return equalObjects(object, other, equalFunc, customizer, bitmask, stack);
+    }
+
+    /**
+     * The base implementation of `_.isMatch` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Array} matchData The property names, values, and compare flags to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     */
+    function baseIsMatch(object, source, matchData, customizer) {
+      var index = matchData.length,
+          length = index,
+          noCustomizer = !customizer;
+
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (index--) {
+        var data = matchData[index];
+        if ((noCustomizer && data[2])
+              ? data[1] !== object[data[0]]
+              : !(data[0] in object)
+            ) {
+          return false;
+        }
+      }
+      while (++index < length) {
+        data = matchData[index];
+        var key = data[0],
+            objValue = object[key],
+            srcValue = data[1];
+
+        if (noCustomizer && data[2]) {
+          if (objValue === undefined && !(key in object)) {
+            return false;
+          }
+        } else {
+          var stack = new Stack;
+          if (customizer) {
+            var result = customizer(objValue, srcValue, key, object, source, stack);
+          }
+          if (!(result === undefined
+                ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack)
+                : result
+              )) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.iteratee`.
+     *
+     * @private
+     * @param {*} [value=_.identity] The value to convert to an iteratee.
+     * @returns {Function} Returns the iteratee.
+     */
+    function baseIteratee(value) {
+      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+      if (typeof value == 'function') {
+        return value;
+      }
+      if (value == null) {
+        return identity;
+      }
+      if (typeof value == 'object') {
+        return isArray(value)
+          ? baseMatchesProperty(value[0], value[1])
+          : baseMatches(value);
+      }
+      return property(value);
+    }
+
+    /**
+     * The base implementation of `_.keys` which doesn't skip the constructor
+     * property of prototypes or treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeys(object) {
+      return nativeKeys(Object(object));
+    }
+
+    /**
+     * The base implementation of `_.keysIn` which doesn't skip the constructor
+     * property of prototypes or treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeysIn(object) {
+      object = object == null ? object : Object(object);
+
+      var result = [];
+      for (var key in object) {
+        result.push(key);
+      }
+      return result;
+    }
+
+    // Fallback for IE < 9 with es6-shim.
+    if (enumerate && !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf')) {
+      baseKeysIn = function(object) {
+        return iteratorToArray(enumerate(object));
+      };
+    }
+
+    /**
+     * The base implementation of `_.lt` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     */
+    function baseLt(value, other) {
+      return value < other;
+    }
+
+    /**
+     * The base implementation of `_.map` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     */
+    function baseMap(collection, iteratee) {
+      var index = -1,
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value, key, collection) {
+        result[++index] = iteratee(value, key, collection);
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.matches` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new function.
+     */
+    function baseMatches(source) {
+      var matchData = getMatchData(source);
+      if (matchData.length == 1 && matchData[0][2]) {
+        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+      }
+      return function(object) {
+        return object === source || baseIsMatch(object, source, matchData);
+      };
+    }
+
+    /**
+     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+     *
+     * @private
+     * @param {string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     */
+    function baseMatchesProperty(path, srcValue) {
+      if (isKey(path) && isStrictComparable(srcValue)) {
+        return matchesStrictComparable(toKey(path), srcValue);
+      }
+      return function(object) {
+        var objValue = get(object, path);
+        return (objValue === undefined && objValue === srcValue)
+          ? hasIn(object, path)
+          : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
+      };
+    }
+
+    /**
+     * The base implementation of `_.merge` without support for multiple sources.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} [customizer] The function to customize merged values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMerge(object, source, srcIndex, customizer, stack) {
+      if (object === source) {
+        return;
+      }
+      if (!(isArray(source) || isTypedArray(source))) {
+        var props = keysIn(source);
+      }
+      arrayEach(props || source, function(srcValue, key) {
+        if (props) {
+          key = srcValue;
+          srcValue = source[key];
+        }
+        if (isObject(srcValue)) {
+          stack || (stack = new Stack);
+          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+        }
+        else {
+          var newValue = customizer
+            ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+            : undefined;
+
+          if (newValue === undefined) {
+            newValue = srcValue;
+          }
+          assignMergeValue(object, key, newValue);
+        }
+      });
+    }
+
+    /**
+     * A specialized version of `baseMerge` for arrays and objects which performs
+     * deep merges and tracks traversed objects enabling objects with circular
+     * references to be merged.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {string} key The key of the value to merge.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} mergeFunc The function to merge values.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+      var objValue = object[key],
+          srcValue = source[key],
+          stacked = stack.get(srcValue);
+
+      if (stacked) {
+        assignMergeValue(object, key, stacked);
+        return;
+      }
+      var newValue = customizer
+        ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+        : undefined;
+
+      var isCommon = newValue === undefined;
+
+      if (isCommon) {
+        newValue = srcValue;
+        if (isArray(srcValue) || isTypedArray(srcValue)) {
+          if (isArray(objValue)) {
+            newValue = objValue;
+          }
+          else if (isArrayLikeObject(objValue)) {
+            newValue = copyArray(objValue);
+          }
+          else {
+            isCommon = false;
+            newValue = baseClone(srcValue, true);
+          }
+        }
+        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+          if (isArguments(objValue)) {
+            newValue = toPlainObject(objValue);
+          }
+          else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+            isCommon = false;
+            newValue = baseClone(srcValue, true);
+          }
+          else {
+            newValue = objValue;
+          }
+        }
+        else {
+          isCommon = false;
+        }
+      }
+      stack.set(srcValue, newValue);
+
+      if (isCommon) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+      }
+      stack['delete'](srcValue);
+      assignMergeValue(object, key, newValue);
+    }
+
+    /**
+     * The base implementation of `_.nth` which doesn't coerce `n` to an integer.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {number} n The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     */
+    function baseNth(array, n) {
+      var length = array.length;
+      if (!length) {
+        return;
+      }
+      n += n < 0 ? length : 0;
+      return isIndex(n, length) ? array[n] : undefined;
+    }
+
+    /**
+     * The base implementation of `_.orderBy` without param guards.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+     * @param {string[]} orders The sort orders of `iteratees`.
+     * @returns {Array} Returns the new sorted array.
+     */
+    function baseOrderBy(collection, iteratees, orders) {
+      var index = -1;
+      iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));
+
+      var result = baseMap(collection, function(value, key, collection) {
+        var criteria = arrayMap(iteratees, function(iteratee) {
+          return iteratee(value);
+        });
+        return { 'criteria': criteria, 'index': ++index, 'value': value };
+      });
+
+      return baseSortBy(result, function(object, other) {
+        return compareMultiple(object, other, orders);
+      });
+    }
+
+    /**
+     * The base implementation of `_.pick` without support for individual
+     * property identifiers.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} props The property identifiers to pick.
+     * @returns {Object} Returns the new object.
+     */
+    function basePick(object, props) {
+      object = Object(object);
+      return arrayReduce(props, function(result, key) {
+        if (key in object) {
+          result[key] = object[key];
+        }
+        return result;
+      }, {});
+    }
+
+    /**
+     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {Function} predicate The function invoked per property.
+     * @returns {Object} Returns the new object.
+     */
+    function basePickBy(object, predicate) {
+      var index = -1,
+          props = getAllKeysIn(object),
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index],
+            value = object[key];
+
+        if (predicate(value, key)) {
+          result[key] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.property` without support for deep paths.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @returns {Function} Returns the new function.
+     */
+    function baseProperty(key) {
+      return function(object) {
+        return object == null ? undefined : object[key];
+      };
+    }
+
+    /**
+     * A specialized version of `baseProperty` which supports deep paths.
+     *
+     * @private
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new function.
+     */
+    function basePropertyDeep(path) {
+      return function(object) {
+        return baseGet(object, path);
+      };
+    }
+
+    /**
+     * The base implementation of `_.pullAllBy` without support for iteratee
+     * shorthands.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAll(array, values, iteratee, comparator) {
+      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+          index = -1,
+          length = values.length,
+          seen = array;
+
+      if (iteratee) {
+        seen = arrayMap(array, baseUnary(iteratee));
+      }
+      while (++index < length) {
+        var fromIndex = 0,
+            value = values[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+          if (seen !== array) {
+            splice.call(seen, fromIndex, 1);
+          }
+          splice.call(array, fromIndex, 1);
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.pullAt` without support for individual
+     * indexes or capturing the removed elements.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {number[]} indexes The indexes of elements to remove.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAt(array, indexes) {
+      var length = array ? indexes.length : 0,
+          lastIndex = length - 1;
+
+      while (length--) {
+        var index = indexes[length];
+        if (length == lastIndex || index !== previous) {
+          var previous = index;
+          if (isIndex(index)) {
+            splice.call(array, index, 1);
+          }
+          else if (!isKey(index, array)) {
+            var path = castPath(index),
+                object = parent(array, path);
+
+            if (object != null) {
+              delete object[toKey(last(path))];
+            }
+          }
+          else {
+            delete array[toKey(index)];
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.random` without support for returning
+     * floating-point numbers.
+     *
+     * @private
+     * @param {number} lower The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the random number.
+     */
+    function baseRandom(lower, upper) {
+      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
+    }
+
+    /**
+     * The base implementation of `_.range` and `_.rangeRight` which doesn't
+     * coerce arguments to numbers.
+     *
+     * @private
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} step The value to increment or decrement by.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the new array of numbers.
+     */
+    function baseRange(start, end, step, fromRight) {
+      var index = -1,
+          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+          result = Array(length);
+
+      while (length--) {
+        result[fromRight ? length : ++index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.repeat` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {string} string The string to repeat.
+     * @param {number} n The number of times to repeat the string.
+     * @returns {string} Returns the repeated string.
+     */
+    function baseRepeat(string, n) {
+      var result = '';
+      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+        return result;
+      }
+      // Leverage the exponentiation by squaring algorithm for a faster repeat.
+      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+      do {
+        if (n % 2) {
+          result += string;
+        }
+        n = nativeFloor(n / 2);
+        if (n) {
+          string += string;
+        }
+      } while (n);
+
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.set`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseSet(object, path, value, customizer) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var index = -1,
+          length = path.length,
+          lastIndex = length - 1,
+          nested = object;
+
+      while (nested != null && ++index < length) {
+        var key = toKey(path[index]);
+        if (isObject(nested)) {
+          var newValue = value;
+          if (index != lastIndex) {
+            var objValue = nested[key];
+            newValue = customizer ? customizer(objValue, key, nested) : undefined;
+            if (newValue === undefined) {
+              newValue = objValue == null
+                ? (isIndex(path[index + 1]) ? [] : {})
+                : objValue;
+            }
+          }
+          assignValue(nested, key, newValue);
+        }
+        nested = nested[key];
+      }
+      return object;
+    }
+
+    /**
+     * The base implementation of `setData` without support for hot loop detection.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetData = !metaMap ? identity : function(func, data) {
+      metaMap.set(func, data);
+      return func;
+    };
+
+    /**
+     * The base implementation of `_.slice` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseSlice(array, start, end) {
+      var index = -1,
+          length = array.length;
+
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = end > length ? length : end;
+      if (end < 0) {
+        end += length;
+      }
+      length = start > end ? 0 : ((end - start) >>> 0);
+      start >>>= 0;
+
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = array[index + start];
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.some` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     */
+    function baseSome(collection, predicate) {
+      var result;
+
+      baseEach(collection, function(value, index, collection) {
+        result = predicate(value, index, collection);
+        return !result;
+      });
+      return !!result;
+    }
+
+    /**
+     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+     * performs a binary search of `array` to determine the index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndex(array, value, retHighest) {
+      var low = 0,
+          high = array ? array.length : low;
+
+      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+        while (low < high) {
+          var mid = (low + high) >>> 1,
+              computed = array[mid];
+
+          if (computed !== null && !isSymbol(computed) &&
+              (retHighest ? (computed <= value) : (computed < value))) {
+            low = mid + 1;
+          } else {
+            high = mid;
+          }
+        }
+        return high;
+      }
+      return baseSortedIndexBy(array, value, identity, retHighest);
+    }
+
+    /**
+     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+     * which invokes `iteratee` for `value` and each element of `array` to compute
+     * their sort ranking. The iteratee is invoked with one argument; (value).
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} iteratee The iteratee invoked per element.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndexBy(array, value, iteratee, retHighest) {
+      value = iteratee(value);
+
+      var low = 0,
+          high = array ? array.length : 0,
+          valIsNaN = value !== value,
+          valIsNull = value === null,
+          valIsSymbol = isSymbol(value),
+          valIsUndefined = value === undefined;
+
+      while (low < high) {
+        var mid = nativeFloor((low + high) / 2),
+            computed = iteratee(array[mid]),
+            othIsDefined = computed !== undefined,
+            othIsNull = computed === null,
+            othIsReflexive = computed === computed,
+            othIsSymbol = isSymbol(computed);
+
+        if (valIsNaN) {
+          var setLow = retHighest || othIsReflexive;
+        } else if (valIsUndefined) {
+          setLow = othIsReflexive && (retHighest || othIsDefined);
+        } else if (valIsNull) {
+          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
+        } else if (valIsSymbol) {
+          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
+        } else if (othIsNull || othIsSymbol) {
+          setLow = false;
+        } else {
+          setLow = retHighest ? (computed <= value) : (computed < value);
+        }
+        if (setLow) {
+          low = mid + 1;
+        } else {
+          high = mid;
+        }
+      }
+      return nativeMin(high, MAX_ARRAY_INDEX);
+    }
+
+    /**
+     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
+     * support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseSortedUniq(array, iteratee) {
+      var index = -1,
+          length = array.length,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        if (!index || !eq(computed, seen)) {
+          var seen = computed;
+          result[resIndex++] = value === 0 ? 0 : value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.toNumber` which doesn't ensure correct
+     * conversions of binary, hexadecimal, or octal string values.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     */
+    function baseToNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      return +value;
+    }
+
+    /**
+     * The base implementation of `_.toString` which doesn't convert nullish
+     * values to empty strings.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {string} Returns the string.
+     */
+    function baseToString(value) {
+      // Exit early for strings to avoid a performance hit in some environments.
+      if (typeof value == 'string') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return symbolToString ? symbolToString.call(value) : '';
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseUniq(array, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          length = array.length,
+          isCommon = true,
+          result = [],
+          seen = result;
+
+      if (comparator) {
+        isCommon = false;
+        includes = arrayIncludesWith;
+      }
+      else if (length >= LARGE_ARRAY_SIZE) {
+        var set = iteratee ? null : createSet(array);
+        if (set) {
+          return setToArray(set);
+        }
+        isCommon = false;
+        includes = cacheHas;
+        seen = new SetCache;
+      }
+      else {
+        seen = iteratee ? [] : result;
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var seenIndex = seen.length;
+          while (seenIndex--) {
+            if (seen[seenIndex] === computed) {
+              continue outer;
+            }
+          }
+          if (iteratee) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+        else if (!includes(seen, computed, comparator)) {
+          if (seen !== result) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.unset`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     */
+    function baseUnset(object, path) {
+      path = isKey(path, object) ? [path] : castPath(path);
+      object = parent(object, path);
+
+      var key = toKey(last(path));
+      return !(object != null && baseHas(object, key)) || delete object[key];
+    }
+
+    /**
+     * The base implementation of `_.update`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to update.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseUpdate(object, path, updater, customizer) {
+      return baseSet(object, path, updater(baseGet(object, path)), customizer);
+    }
+
+    /**
+     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+     * without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {Function} predicate The function invoked per iteration.
+     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseWhile(array, predicate, isDrop, fromRight) {
+      var length = array.length,
+          index = fromRight ? length : -1;
+
+      while ((fromRight ? index-- : ++index < length) &&
+        predicate(array[index], index, array)) {}
+
+      return isDrop
+        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+    }
+
+    /**
+     * The base implementation of `wrapperValue` which returns the result of
+     * performing a sequence of actions on the unwrapped `value`, where each
+     * successive action is supplied the return value of the previous.
+     *
+     * @private
+     * @param {*} value The unwrapped value.
+     * @param {Array} actions Actions to perform to resolve the unwrapped value.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseWrapperValue(value, actions) {
+      var result = value;
+      if (result instanceof LazyWrapper) {
+        result = result.value();
+      }
+      return arrayReduce(actions, function(result, action) {
+        return action.func.apply(action.thisArg, arrayPush([result], action.args));
+      }, result);
+    }
+
+    /**
+     * The base implementation of methods like `_.xor`, without support for
+     * iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of values.
+     */
+    function baseXor(arrays, iteratee, comparator) {
+      var index = -1,
+          length = arrays.length;
+
+      while (++index < length) {
+        var result = result
+          ? arrayPush(
+              baseDifference(result, arrays[index], iteratee, comparator),
+              baseDifference(arrays[index], result, iteratee, comparator)
+            )
+          : arrays[index];
+      }
+      return (result && result.length) ? baseUniq(result, iteratee, comparator) : [];
+    }
+
+    /**
+     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+     *
+     * @private
+     * @param {Array} props The property identifiers.
+     * @param {Array} values The property values.
+     * @param {Function} assignFunc The function to assign values.
+     * @returns {Object} Returns the new object.
+     */
+    function baseZipObject(props, values, assignFunc) {
+      var index = -1,
+          length = props.length,
+          valsLength = values.length,
+          result = {};
+
+      while (++index < length) {
+        var value = index < valsLength ? values[index] : undefined;
+        assignFunc(result, props[index], value);
+      }
+      return result;
+    }
+
+    /**
+     * Casts `value` to an empty array if it's not an array like object.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Array|Object} Returns the cast array-like object.
+     */
+    function castArrayLikeObject(value) {
+      return isArrayLikeObject(value) ? value : [];
+    }
+
+    /**
+     * Casts `value` to `identity` if it's not a function.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Function} Returns cast function.
+     */
+    function castFunction(value) {
+      return typeof value == 'function' ? value : identity;
+    }
+
+    /**
+     * Casts `value` to a path array if it's not one.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Array} Returns the cast property path array.
+     */
+    function castPath(value) {
+      return isArray(value) ? value : stringToPath(value);
+    }
+
+    /**
+     * Casts `array` to a slice if it's needed.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {number} start The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the cast slice.
+     */
+    function castSlice(array, start, end) {
+      var length = array.length;
+      end = end === undefined ? length : end;
+      return (!start && end >= length) ? array : baseSlice(array, start, end);
+    }
+
+    /**
+     * Creates a clone of  `buffer`.
+     *
+     * @private
+     * @param {Buffer} buffer The buffer to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Buffer} Returns the cloned buffer.
+     */
+    function cloneBuffer(buffer, isDeep) {
+      if (isDeep) {
+        return buffer.slice();
+      }
+      var result = new buffer.constructor(buffer.length);
+      buffer.copy(result);
+      return result;
+    }
+
+    /**
+     * Creates a clone of `arrayBuffer`.
+     *
+     * @private
+     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+     * @returns {ArrayBuffer} Returns the cloned array buffer.
+     */
+    function cloneArrayBuffer(arrayBuffer) {
+      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+      return result;
+    }
+
+    /**
+     * Creates a clone of `dataView`.
+     *
+     * @private
+     * @param {Object} dataView The data view to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned data view.
+     */
+    function cloneDataView(dataView, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
+      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
+    }
+
+    /**
+     * Creates a clone of `map`.
+     *
+     * @private
+     * @param {Object} map The map to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned map.
+     */
+    function cloneMap(map, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map);
+      return arrayReduce(array, addMapEntry, new map.constructor);
+    }
+
+    /**
+     * Creates a clone of `regexp`.
+     *
+     * @private
+     * @param {Object} regexp The regexp to clone.
+     * @returns {Object} Returns the cloned regexp.
+     */
+    function cloneRegExp(regexp) {
+      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+      result.lastIndex = regexp.lastIndex;
+      return result;
+    }
+
+    /**
+     * Creates a clone of `set`.
+     *
+     * @private
+     * @param {Object} set The set to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned set.
+     */
+    function cloneSet(set, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set);
+      return arrayReduce(array, addSetEntry, new set.constructor);
+    }
+
+    /**
+     * Creates a clone of the `symbol` object.
+     *
+     * @private
+     * @param {Object} symbol The symbol object to clone.
+     * @returns {Object} Returns the cloned symbol object.
+     */
+    function cloneSymbol(symbol) {
+      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
+    }
+
+    /**
+     * Creates a clone of `typedArray`.
+     *
+     * @private
+     * @param {Object} typedArray The typed array to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned typed array.
+     */
+    function cloneTypedArray(typedArray, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+    }
+
+    /**
+     * Compares values to sort them in ascending order.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {number} Returns the sort order indicator for `value`.
+     */
+    function compareAscending(value, other) {
+      if (value !== other) {
+        var valIsDefined = value !== undefined,
+            valIsNull = value === null,
+            valIsReflexive = value === value,
+            valIsSymbol = isSymbol(value);
+
+        var othIsDefined = other !== undefined,
+            othIsNull = other === null,
+            othIsReflexive = other === other,
+            othIsSymbol = isSymbol(other);
+
+        if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
+            (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
+            (valIsNull && othIsDefined && othIsReflexive) ||
+            (!valIsDefined && othIsReflexive) ||
+            !valIsReflexive) {
+          return 1;
+        }
+        if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
+            (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
+            (othIsNull && valIsDefined && valIsReflexive) ||
+            (!othIsDefined && valIsReflexive) ||
+            !othIsReflexive) {
+          return -1;
+        }
+      }
+      return 0;
+    }
+
+    /**
+     * Used by `_.orderBy` to compare multiple properties of a value to another
+     * and stable sort them.
+     *
+     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+     * specify an order of "desc" for descending or "asc" for ascending sort order
+     * of corresponding values.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {boolean[]|string[]} orders The order to sort by for each property.
+     * @returns {number} Returns the sort order indicator for `object`.
+     */
+    function compareMultiple(object, other, orders) {
+      var index = -1,
+          objCriteria = object.criteria,
+          othCriteria = other.criteria,
+          length = objCriteria.length,
+          ordersLength = orders.length;
+
+      while (++index < length) {
+        var result = compareAscending(objCriteria[index], othCriteria[index]);
+        if (result) {
+          if (index >= ordersLength) {
+            return result;
+          }
+          var order = orders[index];
+          return result * (order == 'desc' ? -1 : 1);
+        }
+      }
+      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+      // that causes it, under certain circumstances, to provide the same value for
+      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+      // for more details.
+      //
+      // This also ensures a stable sort in V8 and other engines.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
+      return object.index - other.index;
+    }
+
+    /**
+     * Creates an array that is the composition of partially applied arguments,
+     * placeholders, and provided arguments into a single array of arguments.
+     *
+     * @private
+     * @param {Array|Object} args The provided arguments.
+     * @param {Array} partials The arguments to prepend to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgs(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersLength = holders.length,
+          leftIndex = -1,
+          leftLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(leftLength + rangeLength),
+          isUncurried = !isCurried;
+
+      while (++leftIndex < leftLength) {
+        result[leftIndex] = partials[leftIndex];
+      }
+      while (++argsIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[holders[argsIndex]] = args[argsIndex];
+        }
+      }
+      while (rangeLength--) {
+        result[leftIndex++] = args[argsIndex++];
+      }
+      return result;
+    }
+
+    /**
+     * This function is like `composeArgs` except that the arguments composition
+     * is tailored for `_.partialRight`.
+     *
+     * @private
+     * @param {Array|Object} args The provided arguments.
+     * @param {Array} partials The arguments to append to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgsRight(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersIndex = -1,
+          holdersLength = holders.length,
+          rightIndex = -1,
+          rightLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(rangeLength + rightLength),
+          isUncurried = !isCurried;
+
+      while (++argsIndex < rangeLength) {
+        result[argsIndex] = args[argsIndex];
+      }
+      var offset = argsIndex;
+      while (++rightIndex < rightLength) {
+        result[offset + rightIndex] = partials[rightIndex];
+      }
+      while (++holdersIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[offset + holders[holdersIndex]] = args[argsIndex++];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Copies the values of `source` to `array`.
+     *
+     * @private
+     * @param {Array} source The array to copy values from.
+     * @param {Array} [array=[]] The array to copy values to.
+     * @returns {Array} Returns `array`.
+     */
+    function copyArray(source, array) {
+      var index = -1,
+          length = source.length;
+
+      array || (array = Array(length));
+      while (++index < length) {
+        array[index] = source[index];
+      }
+      return array;
+    }
+
+    /**
+     * Copies properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy properties from.
+     * @param {Array} props The property identifiers to copy.
+     * @param {Object} [object={}] The object to copy properties to.
+     * @param {Function} [customizer] The function to customize copied values.
+     * @returns {Object} Returns `object`.
+     */
+    function copyObject(source, props, object, customizer) {
+      object || (object = {});
+
+      var index = -1,
+          length = props.length;
+
+      while (++index < length) {
+        var key = props[index];
+
+        var newValue = customizer
+          ? customizer(object[key], source[key], key, object, source)
+          : source[key];
+
+        assignValue(object, key, newValue);
+      }
+      return object;
+    }
+
+    /**
+     * Copies own symbol properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy symbols from.
+     * @param {Object} [object={}] The object to copy symbols to.
+     * @returns {Object} Returns `object`.
+     */
+    function copySymbols(source, object) {
+      return copyObject(source, getSymbols(source), object);
+    }
+
+    /**
+     * Creates a function like `_.groupBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} [initializer] The accumulator object initializer.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter, initializer) {
+      return function(collection, iteratee) {
+        var func = isArray(collection) ? arrayAggregator : baseAggregator,
+            accumulator = initializer ? initializer() : {};
+
+        return func(collection, setter, getIteratee(iteratee), accumulator);
+      };
+    }
+
+    /**
+     * Creates a function like `_.assign`.
+     *
+     * @private
+     * @param {Function} assigner The function to assign values.
+     * @returns {Function} Returns the new assigner function.
+     */
+    function createAssigner(assigner) {
+      return rest(function(object, sources) {
+        var index = -1,
+            length = sources.length,
+            customizer = length > 1 ? sources[length - 1] : undefined,
+            guard = length > 2 ? sources[2] : undefined;
+
+        customizer = typeof customizer == 'function'
+          ? (length--, customizer)
+          : undefined;
+
+        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+          customizer = length < 3 ? undefined : customizer;
+          length = 1;
+        }
+        object = Object(object);
+        while (++index < length) {
+          var source = sources[index];
+          if (source) {
+            assigner(object, source, index, customizer);
+          }
+        }
+        return object;
+      });
+    }
+
+    /**
+     * Creates a `baseEach` or `baseEachRight` function.
+     *
+     * @private
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseEach(eachFunc, fromRight) {
+      return function(collection, iteratee) {
+        if (collection == null) {
+          return collection;
+        }
+        if (!isArrayLike(collection)) {
+          return eachFunc(collection, iteratee);
+        }
+        var length = collection.length,
+            index = fromRight ? length : -1,
+            iterable = Object(collection);
+
+        while ((fromRight ? index-- : ++index < length)) {
+          if (iteratee(iterable[index], index, iterable) === false) {
+            break;
+          }
+        }
+        return collection;
+      };
+    }
+
+    /**
+     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseFor(fromRight) {
+      return function(object, iteratee, keysFunc) {
+        var index = -1,
+            iterable = Object(object),
+            props = keysFunc(object),
+            length = props.length;
+
+        while (length--) {
+          var key = props[fromRight ? length : ++index];
+          if (iteratee(iterable[key], key, iterable) === false) {
+            break;
+          }
+        }
+        return object;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the optional `this`
+     * binding of `thisArg`.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createBaseWrapper(func, bitmask, thisArg) {
+      var isBind = bitmask & BIND_FLAG,
+          Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return fn.apply(isBind ? thisArg : this, arguments);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.lowerFirst`.
+     *
+     * @private
+     * @param {string} methodName The name of the `String` case method to use.
+     * @returns {Function} Returns the new function.
+     */
+    function createCaseFirst(methodName) {
+      return function(string) {
+        string = toString(string);
+
+        var strSymbols = reHasComplexSymbol.test(string)
+          ? stringToArray(string)
+          : undefined;
+
+        var chr = strSymbols
+          ? strSymbols[0]
+          : string.charAt(0);
+
+        var trailing = strSymbols
+          ? castSlice(strSymbols, 1).join('')
+          : string.slice(1);
+
+        return chr[methodName]() + trailing;
+      };
+    }
+
+    /**
+     * Creates a function like `_.camelCase`.
+     *
+     * @private
+     * @param {Function} callback The function to combine each word.
+     * @returns {Function} Returns the new compounder function.
+     */
+    function createCompounder(callback) {
+      return function(string) {
+        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
+      };
+    }
+
+    /**
+     * Creates a function that produces an instance of `Ctor` regardless of
+     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+     *
+     * @private
+     * @param {Function} Ctor The constructor to wrap.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCtorWrapper(Ctor) {
+      return function() {
+        // Use a `switch` statement to work with class constructors. See
+        // http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+        // for more details.
+        var args = arguments;
+        switch (args.length) {
+          case 0: return new Ctor;
+          case 1: return new Ctor(args[0]);
+          case 2: return new Ctor(args[0], args[1]);
+          case 3: return new Ctor(args[0], args[1], args[2]);
+          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+        }
+        var thisBinding = baseCreate(Ctor.prototype),
+            result = Ctor.apply(thisBinding, args);
+
+        // Mimic the constructor's `return` behavior.
+        // See https://es5.github.io/#x13.2.2 for more details.
+        return isObject(result) ? result : thisBinding;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to enable currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {number} arity The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCurryWrapper(func, bitmask, arity) {
+      var Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            args = Array(length),
+            index = length,
+            placeholder = getPlaceholder(wrapper);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
+          ? []
+          : replaceHolders(args, placeholder);
+
+        length -= holders.length;
+        if (length < arity) {
+          return createRecurryWrapper(
+            func, bitmask, createHybridWrapper, wrapper.placeholder, undefined,
+            args, holders, undefined, undefined, arity - length);
+        }
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return apply(fn, this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.flow` or `_.flowRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new flow function.
+     */
+    function createFlow(fromRight) {
+      return rest(function(funcs) {
+        funcs = baseFlatten(funcs, 1);
+
+        var length = funcs.length,
+            index = length,
+            prereq = LodashWrapper.prototype.thru;
+
+        if (fromRight) {
+          funcs.reverse();
+        }
+        while (index--) {
+          var func = funcs[index];
+          if (typeof func != 'function') {
+            throw new TypeError(FUNC_ERROR_TEXT);
+          }
+          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+            var wrapper = new LodashWrapper([], true);
+          }
+        }
+        index = wrapper ? index : length;
+        while (++index < length) {
+          func = funcs[index];
+
+          var funcName = getFuncName(func),
+              data = funcName == 'wrapper' ? getData(func) : undefined;
+
+          if (data && isLaziable(data[0]) &&
+                data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) &&
+                !data[4].length && data[9] == 1
+              ) {
+            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+          } else {
+            wrapper = (func.length == 1 && isLaziable(func))
+              ? wrapper[funcName]()
+              : wrapper.thru(func);
+          }
+        }
+        return function() {
+          var args = arguments,
+              value = args[0];
+
+          if (wrapper && args.length == 1 &&
+              isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
+            return wrapper.plant(value).value();
+          }
+          var index = 0,
+              result = length ? funcs[index].apply(this, args) : value;
+
+          while (++index < length) {
+            result = funcs[index].call(this, result);
+          }
+          return result;
+        };
+      });
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with optional `this`
+     * binding of `thisArg`, partial application, and currying.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [partialsRight] The arguments to append to those provided
+     *  to the new function.
+     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+      var isAry = bitmask & ARY_FLAG,
+          isBind = bitmask & BIND_FLAG,
+          isBindKey = bitmask & BIND_KEY_FLAG,
+          isCurried = bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG),
+          isFlip = bitmask & FLIP_FLAG,
+          Ctor = isBindKey ? undefined : createCtorWrapper(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            index = length,
+            args = Array(length);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        if (isCurried) {
+          var placeholder = getPlaceholder(wrapper),
+              holdersCount = countHolders(args, placeholder);
+        }
+        if (partials) {
+          args = composeArgs(args, partials, holders, isCurried);
+        }
+        if (partialsRight) {
+          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+        }
+        length -= holdersCount;
+        if (isCurried && length < arity) {
+          var newHolders = replaceHolders(args, placeholder);
+          return createRecurryWrapper(
+            func, bitmask, createHybridWrapper, wrapper.placeholder, thisArg,
+            args, newHolders, argPos, ary, arity - length
+          );
+        }
+        var thisBinding = isBind ? thisArg : this,
+            fn = isBindKey ? thisBinding[func] : func;
+
+        length = args.length;
+        if (argPos) {
+          args = reorder(args, argPos);
+        } else if (isFlip && length > 1) {
+          args.reverse();
+        }
+        if (isAry && ary < length) {
+          args.length = ary;
+        }
+        if (this && this !== root && this instanceof wrapper) {
+          fn = Ctor || createCtorWrapper(fn);
+        }
+        return fn.apply(thisBinding, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.invertBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} toIteratee The function to resolve iteratees.
+     * @returns {Function} Returns the new inverter function.
+     */
+    function createInverter(setter, toIteratee) {
+      return function(object, iteratee) {
+        return baseInverter(object, setter, toIteratee(iteratee), {});
+      };
+    }
+
+    /**
+     * Creates a function that performs a mathematical operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @returns {Function} Returns the new mathematical operation function.
+     */
+    function createMathOperation(operator) {
+      return function(value, other) {
+        var result;
+        if (value === undefined && other === undefined) {
+          return 0;
+        }
+        if (value !== undefined) {
+          result = value;
+        }
+        if (other !== undefined) {
+          if (result === undefined) {
+            return other;
+          }
+          if (typeof value == 'string' || typeof other == 'string') {
+            value = baseToString(value);
+            other = baseToString(other);
+          } else {
+            value = baseToNumber(value);
+            other = baseToNumber(other);
+          }
+          result = operator(value, other);
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function like `_.over`.
+     *
+     * @private
+     * @param {Function} arrayFunc The function to iterate over iteratees.
+     * @returns {Function} Returns the new invoker function.
+     */
+    function createOver(arrayFunc) {
+      return rest(function(iteratees) {
+        iteratees = (iteratees.length == 1 && isArray(iteratees[0]))
+          ? arrayMap(iteratees[0], baseUnary(getIteratee()))
+          : arrayMap(baseFlatten(iteratees, 1, isFlattenableIteratee), baseUnary(getIteratee()));
+
+        return rest(function(args) {
+          var thisArg = this;
+          return arrayFunc(iteratees, function(iteratee) {
+            return apply(iteratee, thisArg, args);
+          });
+        });
+      });
+    }
+
+    /**
+     * Creates the padding for `string` based on `length`. The `chars` string
+     * is truncated if the number of characters exceeds `length`.
+     *
+     * @private
+     * @param {number} length The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padding for `string`.
+     */
+    function createPadding(length, chars) {
+      chars = chars === undefined ? ' ' : baseToString(chars);
+
+      var charsLength = chars.length;
+      if (charsLength < 2) {
+        return charsLength ? baseRepeat(chars, length) : chars;
+      }
+      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
+      return reHasComplexSymbol.test(chars)
+        ? castSlice(stringToArray(result), 0, length).join('')
+        : result.slice(0, length);
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the `this` binding
+     * of `thisArg` and `partials` prepended to the arguments it receives.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {Array} partials The arguments to prepend to those provided to
+     *  the new function.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createPartialWrapper(func, bitmask, thisArg, partials) {
+      var isBind = bitmask & BIND_FLAG,
+          Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var argsIndex = -1,
+            argsLength = arguments.length,
+            leftIndex = -1,
+            leftLength = partials.length,
+            args = Array(leftLength + argsLength),
+            fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+        while (++leftIndex < leftLength) {
+          args[leftIndex] = partials[leftIndex];
+        }
+        while (argsLength--) {
+          args[leftIndex++] = arguments[++argsIndex];
+        }
+        return apply(fn, isBind ? thisArg : this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.range` or `_.rangeRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new range function.
+     */
+    function createRange(fromRight) {
+      return function(start, end, step) {
+        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+          end = step = undefined;
+        }
+        // Ensure the sign of `-0` is preserved.
+        start = toNumber(start);
+        start = start === start ? start : 0;
+        if (end === undefined) {
+          end = start;
+          start = 0;
+        } else {
+          end = toNumber(end) || 0;
+        }
+        step = step === undefined ? (start < end ? 1 : -1) : (toNumber(step) || 0);
+        return baseRange(start, end, step, fromRight);
+      };
+    }
+
+    /**
+     * Creates a function that performs a relational operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @returns {Function} Returns the new relational operation function.
+     */
+    function createRelationalOperation(operator) {
+      return function(value, other) {
+        if (!(typeof value == 'string' && typeof other == 'string')) {
+          value = toNumber(value);
+          other = toNumber(other);
+        }
+        return operator(value, other);
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to continue currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {Function} wrapFunc The function to create the `func` wrapper.
+     * @param {*} placeholder The placeholder value.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createRecurryWrapper(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+      var isCurry = bitmask & CURRY_FLAG,
+          newHolders = isCurry ? holders : undefined,
+          newHoldersRight = isCurry ? undefined : holders,
+          newPartials = isCurry ? partials : undefined,
+          newPartialsRight = isCurry ? undefined : partials;
+
+      bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
+      bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
+
+      if (!(bitmask & CURRY_BOUND_FLAG)) {
+        bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
+      }
+      var newData = [
+        func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
+        newHoldersRight, argPos, ary, arity
+      ];
+
+      var result = wrapFunc.apply(undefined, newData);
+      if (isLaziable(func)) {
+        setData(result, newData);
+      }
+      result.placeholder = placeholder;
+      return result;
+    }
+
+    /**
+     * Creates a function like `_.round`.
+     *
+     * @private
+     * @param {string} methodName The name of the `Math` method to use when rounding.
+     * @returns {Function} Returns the new round function.
+     */
+    function createRound(methodName) {
+      var func = Math[methodName];
+      return function(number, precision) {
+        number = toNumber(number);
+        precision = toInteger(precision);
+        if (precision) {
+          // Shift with exponential notation to avoid floating-point issues.
+          // See [MDN](https://mdn.io/round#Examples) for more details.
+          var pair = (toString(number) + 'e').split('e'),
+              value = func(pair[0] + 'e' + (+pair[1] + precision));
+
+          pair = (toString(value) + 'e').split('e');
+          return +(pair[0] + 'e' + (+pair[1] - precision));
+        }
+        return func(number);
+      };
+    }
+
+    /**
+     * Creates a set of `values`.
+     *
+     * @private
+     * @param {Array} values The values to add to the set.
+     * @returns {Object} Returns the new set.
+     */
+    var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
+      return new Set(values);
+    };
+
+    /**
+     * Creates a function that either curries or invokes `func` with optional
+     * `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags.
+     *  The bitmask may be composed of the following flags:
+     *     1 - `_.bind`
+     *     2 - `_.bindKey`
+     *     4 - `_.curry` or `_.curryRight` of a bound function
+     *     8 - `_.curry`
+     *    16 - `_.curryRight`
+     *    32 - `_.partial`
+     *    64 - `_.partialRight`
+     *   128 - `_.rearg`
+     *   256 - `_.ary`
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to be partially applied.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+      var isBindKey = bitmask & BIND_KEY_FLAG;
+      if (!isBindKey && typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var length = partials ? partials.length : 0;
+      if (!length) {
+        bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
+        partials = holders = undefined;
+      }
+      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
+      arity = arity === undefined ? arity : toInteger(arity);
+      length -= holders ? holders.length : 0;
+
+      if (bitmask & PARTIAL_RIGHT_FLAG) {
+        var partialsRight = partials,
+            holdersRight = holders;
+
+        partials = holders = undefined;
+      }
+      var data = isBindKey ? undefined : getData(func);
+
+      var newData = [
+        func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
+        argPos, ary, arity
+      ];
+
+      if (data) {
+        mergeData(newData, data);
+      }
+      func = newData[0];
+      bitmask = newData[1];
+      thisArg = newData[2];
+      partials = newData[3];
+      holders = newData[4];
+      arity = newData[9] = newData[9] == null
+        ? (isBindKey ? 0 : func.length)
+        : nativeMax(newData[9] - length, 0);
+
+      if (!arity && bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG)) {
+        bitmask &= ~(CURRY_FLAG | CURRY_RIGHT_FLAG);
+      }
+      if (!bitmask || bitmask == BIND_FLAG) {
+        var result = createBaseWrapper(func, bitmask, thisArg);
+      } else if (bitmask == CURRY_FLAG || bitmask == CURRY_RIGHT_FLAG) {
+        result = createCurryWrapper(func, bitmask, arity);
+      } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !holders.length) {
+        result = createPartialWrapper(func, bitmask, thisArg, partials);
+      } else {
+        result = createHybridWrapper.apply(undefined, newData);
+      }
+      var setter = data ? baseSetData : setData;
+      return setter(result, newData);
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for arrays with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Array} array The array to compare.
+     * @param {Array} other The other array to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} stack Tracks traversed `array` and `other` objects.
+     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+     */
+    function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
+      var index = -1,
+          isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+          isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
+          arrLength = array.length,
+          othLength = other.length;
+
+      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+        return false;
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(array);
+      if (stacked) {
+        return stacked == other;
+      }
+      var result = true;
+      stack.set(array, other);
+
+      // Ignore non-index properties.
+      while (++index < arrLength) {
+        var arrValue = array[index],
+            othValue = other[index];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, arrValue, index, other, array, stack)
+            : customizer(arrValue, othValue, index, array, other, stack);
+        }
+        if (compared !== undefined) {
+          if (compared) {
+            continue;
+          }
+          result = false;
+          break;
+        }
+        // Recursively compare arrays (susceptible to call stack limits).
+        if (isUnordered) {
+          if (!arraySome(other, function(othValue) {
+                return arrValue === othValue ||
+                  equalFunc(arrValue, othValue, customizer, bitmask, stack);
+              })) {
+            result = false;
+            break;
+          }
+        } else if (!(
+              arrValue === othValue ||
+                equalFunc(arrValue, othValue, customizer, bitmask, stack)
+            )) {
+          result = false;
+          break;
+        }
+      }
+      stack['delete'](array);
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for comparing objects of
+     * the same `toStringTag`.
+     *
+     * **Note:** This function only supports comparing values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {string} tag The `toStringTag` of the objects to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) {
+      switch (tag) {
+        case dataViewTag:
+          if ((object.byteLength != other.byteLength) ||
+              (object.byteOffset != other.byteOffset)) {
+            return false;
+          }
+          object = object.buffer;
+          other = other.buffer;
+
+        case arrayBufferTag:
+          if ((object.byteLength != other.byteLength) ||
+              !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+            return false;
+          }
+          return true;
+
+        case boolTag:
+        case dateTag:
+          // 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 +object == +other;
+
+        case errorTag:
+          return object.name == other.name && object.message == other.message;
+
+        case numberTag:
+          // Treat `NaN` vs. `NaN` as equal.
+          return (object != +object) ? other != +other : object == +other;
+
+        case regexpTag:
+        case stringTag:
+          // Coerce regexes to strings and treat strings, primitives and objects,
+          // as equal. See http://www.ecma-international.org/ecma-262/6.0/#sec-regexp.prototype.tostring
+          // for more details.
+          return object == (other + '');
+
+        case mapTag:
+          var convert = mapToArray;
+
+        case setTag:
+          var isPartial = bitmask & PARTIAL_COMPARE_FLAG;
+          convert || (convert = setToArray);
+
+          if (object.size != other.size && !isPartial) {
+            return false;
+          }
+          // Assume cyclic values are equal.
+          var stacked = stack.get(object);
+          if (stacked) {
+            return stacked == other;
+          }
+          bitmask |= UNORDERED_COMPARE_FLAG;
+          stack.set(object, other);
+
+          // Recursively compare objects (susceptible to call stack limits).
+          return equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack);
+
+        case symbolTag:
+          if (symbolValueOf) {
+            return symbolValueOf.call(object) == symbolValueOf.call(other);
+          }
+      }
+      return false;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for objects with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
+      var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+          objProps = keys(object),
+          objLength = objProps.length,
+          othProps = keys(other),
+          othLength = othProps.length;
+
+      if (objLength != othLength && !isPartial) {
+        return false;
+      }
+      var index = objLength;
+      while (index--) {
+        var key = objProps[index];
+        if (!(isPartial ? key in other : baseHas(other, key))) {
+          return false;
+        }
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(object);
+      if (stacked) {
+        return stacked == other;
+      }
+      var result = true;
+      stack.set(object, other);
+
+      var skipCtor = isPartial;
+      while (++index < objLength) {
+        key = objProps[index];
+        var objValue = object[key],
+            othValue = other[key];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, objValue, key, other, object, stack)
+            : customizer(objValue, othValue, key, object, other, stack);
+        }
+        // Recursively compare objects (susceptible to call stack limits).
+        if (!(compared === undefined
+              ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
+              : compared
+            )) {
+          result = false;
+          break;
+        }
+        skipCtor || (skipCtor = key == 'constructor');
+      }
+      if (result && !skipCtor) {
+        var objCtor = object.constructor,
+            othCtor = other.constructor;
+
+        // Non `Object` object instances with different constructors are not equal.
+        if (objCtor != othCtor &&
+            ('constructor' in object && 'constructor' in other) &&
+            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+          result = false;
+        }
+      }
+      stack['delete'](object);
+      return result;
+    }
+
+    /**
+     * Creates an array of own enumerable property names and symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeys(object) {
+      return baseGetAllKeys(object, keys, getSymbols);
+    }
+
+    /**
+     * Creates an array of own and inherited enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeysIn(object) {
+      return baseGetAllKeys(object, keysIn, getSymbolsIn);
+    }
+
+    /**
+     * Gets metadata for `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {*} Returns the metadata for `func`.
+     */
+    var getData = !metaMap ? noop : function(func) {
+      return metaMap.get(func);
+    };
+
+    /**
+     * Gets the name of `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {string} Returns the function name.
+     */
+    function getFuncName(func) {
+      var result = (func.name + ''),
+          array = realNames[result],
+          length = hasOwnProperty.call(realNames, result) ? array.length : 0;
+
+      while (length--) {
+        var data = array[length],
+            otherFunc = data.func;
+        if (otherFunc == null || otherFunc == func) {
+          return data.name;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
+     * this function returns the custom method, otherwise it returns `baseIteratee`.
+     * If arguments are provided, the chosen function is invoked with them and
+     * its result is returned.
+     *
+     * @private
+     * @param {*} [value] The value to convert to an iteratee.
+     * @param {number} [arity] The arity of the created iteratee.
+     * @returns {Function} Returns the chosen function or its result.
+     */
+    function getIteratee() {
+      var result = lodash.iteratee || iteratee;
+      result = result === iteratee ? baseIteratee : result;
+      return arguments.length ? result(arguments[0], arguments[1]) : result;
+    }
+
+    /**
+     * Gets the "length" property value of `object`.
+     *
+     * **Note:** This function is used to avoid a
+     * [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) that affects
+     * Safari on at least iOS 8.1-8.3 ARM64.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {*} Returns the "length" value.
+     */
+    var getLength = baseProperty('length');
+
+    /**
+     * Gets the property names, values, and compare flags of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the match data of `object`.
+     */
+    function getMatchData(object) {
+      var result = toPairs(object),
+          length = result.length;
+
+      while (length--) {
+        result[length][2] = isStrictComparable(result[length][1]);
+      }
+      return result;
+    }
+
+    /**
+     * Gets the native function at `key` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {string} key The key of the method to get.
+     * @returns {*} Returns the function if it's native, else `undefined`.
+     */
+    function getNative(object, key) {
+      var value = object[key];
+      return isNative(value) ? value : undefined;
+    }
+
+    /**
+     * Gets the argument placeholder value for `func`.
+     *
+     * @private
+     * @param {Function} func The function to inspect.
+     * @returns {*} Returns the placeholder value.
+     */
+    function getPlaceholder(func) {
+      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+      return object.placeholder;
+    }
+
+    /**
+     * Gets the `[[Prototype]]` of `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {null|Object} Returns the `[[Prototype]]`.
+     */
+    function getPrototype(value) {
+      return nativeGetPrototype(Object(value));
+    }
+
+    /**
+     * Creates an array of the own enumerable symbol properties of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    function getSymbols(object) {
+      // Coerce `object` to an object to avoid non-object errors in V8.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=3443 for more details.
+      return getOwnPropertySymbols(Object(object));
+    }
+
+    // Fallback for IE < 11.
+    if (!getOwnPropertySymbols) {
+      getSymbols = function() {
+        return [];
+      };
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable symbol properties
+     * of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    var getSymbolsIn = !getOwnPropertySymbols ? getSymbols : function(object) {
+      var result = [];
+      while (object) {
+        arrayPush(result, getSymbols(object));
+        object = getPrototype(object);
+      }
+      return result;
+    };
+
+    /**
+     * Gets the `toStringTag` of `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the `toStringTag`.
+     */
+    function getTag(value) {
+      return objectToString.call(value);
+    }
+
+    // Fallback for data views, maps, sets, and weak maps in IE 11,
+    // for data views in Edge, and promises in Node.js.
+    if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
+        (Map && getTag(new Map) != mapTag) ||
+        (Promise && getTag(Promise.resolve()) != promiseTag) ||
+        (Set && getTag(new Set) != setTag) ||
+        (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+      getTag = function(value) {
+        var result = objectToString.call(value),
+            Ctor = result == objectTag ? value.constructor : undefined,
+            ctorString = Ctor ? toSource(Ctor) : undefined;
+
+        if (ctorString) {
+          switch (ctorString) {
+            case dataViewCtorString: return dataViewTag;
+            case mapCtorString: return mapTag;
+            case promiseCtorString: return promiseTag;
+            case setCtorString: return setTag;
+            case weakMapCtorString: return weakMapTag;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Gets the view, applying any `transforms` to the `start` and `end` positions.
+     *
+     * @private
+     * @param {number} start The start of the view.
+     * @param {number} end The end of the view.
+     * @param {Array} transforms The transformations to apply to the view.
+     * @returns {Object} Returns an object containing the `start` and `end`
+     *  positions of the view.
+     */
+    function getView(start, end, transforms) {
+      var index = -1,
+          length = transforms.length;
+
+      while (++index < length) {
+        var data = transforms[index],
+            size = data.size;
+
+        switch (data.type) {
+          case 'drop':      start += size; break;
+          case 'dropRight': end -= size; break;
+          case 'take':      end = nativeMin(end, start + size); break;
+          case 'takeRight': start = nativeMax(start, end - size); break;
+        }
+      }
+      return { 'start': start, 'end': end };
+    }
+
+    /**
+     * Checks if `path` exists on `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @param {Function} hasFunc The function to check properties.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     */
+    function hasPath(object, path, hasFunc) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var result,
+          index = -1,
+          length = path.length;
+
+      while (++index < length) {
+        var key = toKey(path[index]);
+        if (!(result = object != null && hasFunc(object, key))) {
+          break;
+        }
+        object = object[key];
+      }
+      if (result) {
+        return result;
+      }
+      var length = object ? object.length : 0;
+      return !!length && isLength(length) && isIndex(key, length) &&
+        (isArray(object) || isString(object) || isArguments(object));
+    }
+
+    /**
+     * Initializes an array clone.
+     *
+     * @private
+     * @param {Array} array The array to clone.
+     * @returns {Array} Returns the initialized clone.
+     */
+    function initCloneArray(array) {
+      var length = array.length,
+          result = array.constructor(length);
+
+      // Add properties assigned by `RegExp#exec`.
+      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+        result.index = array.index;
+        result.input = array.input;
+      }
+      return result;
+    }
+
+    /**
+     * Initializes an object clone.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneObject(object) {
+      return (typeof object.constructor == 'function' && !isPrototype(object))
+        ? baseCreate(getPrototype(object))
+        : {};
+    }
+
+    /**
+     * Initializes an object clone based on its `toStringTag`.
+     *
+     * **Note:** This function only supports cloning values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @param {string} tag The `toStringTag` of the object to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneByTag(object, tag, cloneFunc, isDeep) {
+      var Ctor = object.constructor;
+      switch (tag) {
+        case arrayBufferTag:
+          return cloneArrayBuffer(object);
+
+        case boolTag:
+        case dateTag:
+          return new Ctor(+object);
+
+        case dataViewTag:
+          return cloneDataView(object, isDeep);
+
+        case float32Tag: case float64Tag:
+        case int8Tag: case int16Tag: case int32Tag:
+        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+          return cloneTypedArray(object, isDeep);
+
+        case mapTag:
+          return cloneMap(object, isDeep, cloneFunc);
+
+        case numberTag:
+        case stringTag:
+          return new Ctor(object);
+
+        case regexpTag:
+          return cloneRegExp(object);
+
+        case setTag:
+          return cloneSet(object, isDeep, cloneFunc);
+
+        case symbolTag:
+          return cloneSymbol(object);
+      }
+    }
+
+    /**
+     * Creates an array of index keys for `object` values of arrays,
+     * `arguments` objects, and strings, otherwise `null` is returned.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array|null} Returns index keys, else `null`.
+     */
+    function indexKeys(object) {
+      var length = object ? object.length : undefined;
+      if (isLength(length) &&
+          (isArray(object) || isString(object) || isArguments(object))) {
+        return baseTimes(length, String);
+      }
+      return null;
+    }
+
+    /**
+     * Checks if `value` is a flattenable `arguments` object or array.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+     */
+    function isFlattenable(value) {
+      return isArrayLikeObject(value) && (isArray(value) || isArguments(value));
+    }
+
+    /**
+     * Checks if `value` is a flattenable array and not a `_.matchesProperty`
+     * iteratee shorthand.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+     */
+    function isFlattenableIteratee(value) {
+      return isArray(value) && !(value.length == 2 && !isFunction(value[0]));
+    }
+
+    /**
+     * Checks if `value` is a valid array-like index.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+     */
+    function isIndex(value, length) {
+      length = length == null ? MAX_SAFE_INTEGER : length;
+      return !!length &&
+        (typeof value == 'number' || reIsUint.test(value)) &&
+        (value > -1 && value % 1 == 0 && value < length);
+    }
+
+    /**
+     * Checks if the given arguments are from an iteratee call.
+     *
+     * @private
+     * @param {*} value The potential iteratee value argument.
+     * @param {*} index The potential iteratee index or key argument.
+     * @param {*} object The potential iteratee object argument.
+     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+     *  else `false`.
+     */
+    function isIterateeCall(value, index, object) {
+      if (!isObject(object)) {
+        return false;
+      }
+      var type = typeof index;
+      if (type == 'number'
+            ? (isArrayLike(object) && isIndex(index, object.length))
+            : (type == 'string' && index in object)
+          ) {
+        return eq(object[index], value);
+      }
+      return false;
+    }
+
+    /**
+     * Checks if `value` is a property name and not a property path.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+     */
+    function isKey(value, object) {
+      if (isArray(value)) {
+        return false;
+      }
+      var type = typeof value;
+      if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+          value == null || isSymbol(value)) {
+        return true;
+      }
+      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+        (object != null && value in Object(object));
+    }
+
+    /**
+     * Checks if `value` is suitable for use as unique object key.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+     */
+    function isKeyable(value) {
+      var type = typeof value;
+      return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+        ? (value !== '__proto__')
+        : (value === null);
+    }
+
+    /**
+     * Checks if `func` has a lazy counterpart.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
+     *  else `false`.
+     */
+    function isLaziable(func) {
+      var funcName = getFuncName(func),
+          other = lodash[funcName];
+
+      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+        return false;
+      }
+      if (func === other) {
+        return true;
+      }
+      var data = getData(other);
+      return !!data && func === data[0];
+    }
+
+    /**
+     * Checks if `value` is likely a prototype object.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+     */
+    function isPrototype(value) {
+      var Ctor = value && value.constructor,
+          proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+      return value === proto;
+    }
+
+    /**
+     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` if suitable for strict
+     *  equality comparisons, else `false`.
+     */
+    function isStrictComparable(value) {
+      return value === value && !isObject(value);
+    }
+
+    /**
+     * A specialized version of `matchesProperty` for source values suitable
+     * for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     */
+    function matchesStrictComparable(key, srcValue) {
+      return function(object) {
+        if (object == null) {
+          return false;
+        }
+        return object[key] === srcValue &&
+          (srcValue !== undefined || (key in Object(object)));
+      };
+    }
+
+    /**
+     * Merges the function metadata of `source` into `data`.
+     *
+     * Merging metadata reduces the number of wrappers used to invoke a function.
+     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+     * may be applied regardless of execution order. Methods like `_.ary` and
+     * `_.rearg` modify function arguments, making the order in which they are
+     * executed important, preventing the merging of metadata. However, we make
+     * an exception for a safe combined case where curried functions have `_.ary`
+     * and or `_.rearg` applied.
+     *
+     * @private
+     * @param {Array} data The destination metadata.
+     * @param {Array} source The source metadata.
+     * @returns {Array} Returns `data`.
+     */
+    function mergeData(data, source) {
+      var bitmask = data[1],
+          srcBitmask = source[1],
+          newBitmask = bitmask | srcBitmask,
+          isCommon = newBitmask < (BIND_FLAG | BIND_KEY_FLAG | ARY_FLAG);
+
+      var isCombo =
+        ((srcBitmask == ARY_FLAG) && (bitmask == CURRY_FLAG)) ||
+        ((srcBitmask == ARY_FLAG) && (bitmask == REARG_FLAG) && (data[7].length <= source[8])) ||
+        ((srcBitmask == (ARY_FLAG | REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == CURRY_FLAG));
+
+      // Exit early if metadata can't be merged.
+      if (!(isCommon || isCombo)) {
+        return data;
+      }
+      // Use source `thisArg` if available.
+      if (srcBitmask & BIND_FLAG) {
+        data[2] = source[2];
+        // Set when currying a bound function.
+        newBitmask |= bitmask & BIND_FLAG ? 0 : CURRY_BOUND_FLAG;
+      }
+      // Compose partial arguments.
+      var value = source[3];
+      if (value) {
+        var partials = data[3];
+        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
+        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
+      }
+      // Compose partial right arguments.
+      value = source[5];
+      if (value) {
+        partials = data[5];
+        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
+        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
+      }
+      // Use source `argPos` if available.
+      value = source[7];
+      if (value) {
+        data[7] = value;
+      }
+      // Use source `ary` if it's smaller.
+      if (srcBitmask & ARY_FLAG) {
+        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+      }
+      // Use source `arity` if one is not provided.
+      if (data[9] == null) {
+        data[9] = source[9];
+      }
+      // Use source `func` and merge bitmasks.
+      data[0] = source[0];
+      data[1] = newBitmask;
+
+      return data;
+    }
+
+    /**
+     * Used by `_.defaultsDeep` to customize its `_.merge` use.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to merge.
+     * @param {Object} object The parent object of `objValue`.
+     * @param {Object} source The parent object of `srcValue`.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     * @returns {*} Returns the value to assign.
+     */
+    function mergeDefaults(objValue, srcValue, key, object, source, stack) {
+      if (isObject(objValue) && isObject(srcValue)) {
+        baseMerge(objValue, srcValue, undefined, mergeDefaults, stack.set(srcValue, objValue));
+      }
+      return objValue;
+    }
+
+    /**
+     * Gets the parent value at `path` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array} path The path to get the parent value of.
+     * @returns {*} Returns the parent value.
+     */
+    function parent(object, path) {
+      return path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+    }
+
+    /**
+     * Reorder `array` according to the specified indexes where the element at
+     * the first index is assigned as the first element, the element at
+     * the second index is assigned as the second element, and so on.
+     *
+     * @private
+     * @param {Array} array The array to reorder.
+     * @param {Array} indexes The arranged array indexes.
+     * @returns {Array} Returns `array`.
+     */
+    function reorder(array, indexes) {
+      var arrLength = array.length,
+          length = nativeMin(indexes.length, arrLength),
+          oldArray = copyArray(array);
+
+      while (length--) {
+        var index = indexes[length];
+        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+      }
+      return array;
+    }
+
+    /**
+     * Sets metadata for `func`.
+     *
+     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+     * period of time, it will trip its breaker and transition to an identity
+     * function to avoid garbage collection pauses in V8. See
+     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
+     * for more details.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var setData = (function() {
+      var count = 0,
+          lastCalled = 0;
+
+      return function(key, value) {
+        var stamp = now(),
+            remaining = HOT_SPAN - (stamp - lastCalled);
+
+        lastCalled = stamp;
+        if (remaining > 0) {
+          if (++count >= HOT_COUNT) {
+            return key;
+          }
+        } else {
+          count = 0;
+        }
+        return baseSetData(key, value);
+      };
+    }());
+
+    /**
+     * Converts `string` to a property path array.
+     *
+     * @private
+     * @param {string} string The string to convert.
+     * @returns {Array} Returns the property path array.
+     */
+    var stringToPath = memoize(function(string) {
+      var result = [];
+      toString(string).replace(rePropName, function(match, number, quote, string) {
+        result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+      });
+      return result;
+    });
+
+    /**
+     * Converts `value` to a string key if it's not a string or symbol.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {string|symbol} Returns the key.
+     */
+    function toKey(value) {
+      if (typeof value == 'string' || isSymbol(value)) {
+        return value;
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * Converts `func` to its source code.
+     *
+     * @private
+     * @param {Function} func The function to process.
+     * @returns {string} Returns the source code.
+     */
+    function toSource(func) {
+      if (func != null) {
+        try {
+          return funcToString.call(func);
+        } catch (e) {}
+        try {
+          return (func + '');
+        } catch (e) {}
+      }
+      return '';
+    }
+
+    /**
+     * Creates a clone of `wrapper`.
+     *
+     * @private
+     * @param {Object} wrapper The wrapper to clone.
+     * @returns {Object} Returns the cloned wrapper.
+     */
+    function wrapperClone(wrapper) {
+      if (wrapper instanceof LazyWrapper) {
+        return wrapper.clone();
+      }
+      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+      result.__actions__ = copyArray(wrapper.__actions__);
+      result.__index__  = wrapper.__index__;
+      result.__values__ = wrapper.__values__;
+      return result;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements split into groups the length of `size`.
+     * If `array` can't be split evenly, the final chunk will be the remaining
+     * elements.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to process.
+     * @param {number} [size=1] The length of each chunk
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the new array containing chunks.
+     * @example
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 2);
+     * // => [['a', 'b'], ['c', 'd']]
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 3);
+     * // => [['a', 'b', 'c'], ['d']]
+     */
+    function chunk(array, size, guard) {
+      if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
+        size = 1;
+      } else {
+        size = nativeMax(toInteger(size), 0);
+      }
+      var length = array ? array.length : 0;
+      if (!length || size < 1) {
+        return [];
+      }
+      var index = 0,
+          resIndex = 0,
+          result = Array(nativeCeil(length / size));
+
+      while (index < length) {
+        result[resIndex++] = baseSlice(array, index, (index += size));
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are falsey.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array ? array.length : 0,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result[resIndex++] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates a new array concatenating `array` with any additional arrays
+     * and/or values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to concatenate.
+     * @param {...*} [values] The values to concatenate.
+     * @returns {Array} Returns the new concatenated array.
+     * @example
+     *
+     * var array = [1];
+     * var other = _.concat(array, 2, [3], [[4]]);
+     *
+     * console.log(other);
+     * // => [1, 2, 3, [4]]
+     *
+     * console.log(array);
+     * // => [1]
+     */
+    function concat() {
+      var length = arguments.length,
+          array = castArray(arguments[0]);
+
+      if (length < 2) {
+        return length ? copyArray(array) : [];
+      }
+      var args = Array(length - 1);
+      while (length--) {
+        args[length - 1] = arguments[length];
+      }
+      return arrayConcat(array, baseFlatten(args, 1));
+    }
+
+    /**
+     * Creates an array of unique `array` values not included in the other given
+     * arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. The order of result values is determined by the
+     * order they occur in the first array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.without, _.xor
+     * @example
+     *
+     * _.difference([3, 2, 1], [4, 2]);
+     * // => [3, 1]
+     */
+    var difference = rest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `iteratee` which
+     * is invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. Result values are chosen from the first array.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
+     * // => [3.1, 1.3]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var differenceBy = rest(function(array, values) {
+      var iteratee = last(values);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `comparator`
+     * which is invoked to compare elements of `array` to `values`. Result values
+     * are chosen from the first array. The comparator is invoked with two arguments:
+     * (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     *
+     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }]
+     */
+    var differenceWith = rest(function(array, values) {
+      var comparator = last(values);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.drop([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.drop([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.drop([1, 2, 3], 5);
+     * // => []
+     *
+     * _.drop([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function drop(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropRight([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.dropRight([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.dropRight([1, 2, 3], 5);
+     * // => []
+     *
+     * _.dropRight([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function dropRight(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the end.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.dropRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropRightWhile(users, ['active', false]);
+     * // => objects for ['barney']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropRightWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the beginning.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.dropWhile(users, function(o) { return !o.active; });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropWhile(users, ['active', false]);
+     * // => objects for ['pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true)
+        : [];
+    }
+
+    /**
+     * Fills elements of `array` with `value` from `start` up to, but not
+     * including, `end`.
+     *
+     * **Note:** This method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Array
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.fill(array, 'a');
+     * console.log(array);
+     * // => ['a', 'a', 'a']
+     *
+     * _.fill(Array(3), 2);
+     * // => [2, 2, 2]
+     *
+     * _.fill([4, 6, 8, 10], '*', 1, 3);
+     * // => [4, '*', '*', 10]
+     */
+    function fill(array, value, start, end) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+        start = 0;
+        end = length;
+      }
+      return baseFill(array, value, start, end);
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.findIndex(users, function(o) { return o.user == 'barney'; });
+     * // => 0
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findIndex(users, { 'user': 'fred', 'active': false });
+     * // => 1
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findIndex(users, ['active', false]);
+     * // => 0
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findIndex(users, 'active');
+     * // => 2
+     */
+    function findIndex(array, predicate) {
+      return (array && array.length)
+        ? baseFindIndex(array, getIteratee(predicate, 3))
+        : -1;
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+     * // => 2
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+     * // => 0
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastIndex(users, ['active', false]);
+     * // => 2
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastIndex(users, 'active');
+     * // => 0
+     */
+    function findLastIndex(array, predicate) {
+      return (array && array.length)
+        ? baseFindIndex(array, getIteratee(predicate, 3), true)
+        : -1;
+    }
+
+    /**
+     * Flattens `array` a single level deep.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, [3, [4]], 5]
+     */
+    function flatten(array) {
+      var length = array ? array.length : 0;
+      return length ? baseFlatten(array, 1) : [];
+    }
+
+    /**
+     * Recursively flattens `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flattenDeep([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, 3, 4, 5]
+     */
+    function flattenDeep(array) {
+      var length = array ? array.length : 0;
+      return length ? baseFlatten(array, INFINITY) : [];
+    }
+
+    /**
+     * Recursively flatten `array` up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * var array = [1, [2, [3, [4]], 5]];
+     *
+     * _.flattenDepth(array, 1);
+     * // => [1, 2, [3, [4]], 5]
+     *
+     * _.flattenDepth(array, 2);
+     * // => [1, 2, 3, [4], 5]
+     */
+    function flattenDepth(array, depth) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(array, depth);
+    }
+
+    /**
+     * The inverse of `_.toPairs`; this method returns an object composed
+     * from key-value `pairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} pairs The key-value pairs.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.fromPairs([['fred', 30], ['barney', 40]]);
+     * // => { 'fred': 30, 'barney': 40 }
+     */
+    function fromPairs(pairs) {
+      var index = -1,
+          length = pairs ? pairs.length : 0,
+          result = {};
+
+      while (++index < length) {
+        var pair = pairs[index];
+        result[pair[0]] = pair[1];
+      }
+      return result;
+    }
+
+    /**
+     * Gets the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias first
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the first element of `array`.
+     * @example
+     *
+     * _.head([1, 2, 3]);
+     * // => 1
+     *
+     * _.head([]);
+     * // => undefined
+     */
+    function head(array) {
+      return (array && array.length) ? array[0] : undefined;
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found in `array`
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. If `fromIndex` is negative, it's used as the
+     * offset from the end of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 1, 2], 2);
+     * // => 1
+     *
+     * // Search from the `fromIndex`.
+     * _.indexOf([1, 2, 1, 2], 2, 2);
+     * // => 3
+     */
+    function indexOf(array, value, fromIndex) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return -1;
+      }
+      fromIndex = toInteger(fromIndex);
+      if (fromIndex < 0) {
+        fromIndex = nativeMax(length + fromIndex, 0);
+      }
+      return baseIndexOf(array, value, fromIndex);
+    }
+
+    /**
+     * Gets all but the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     */
+    function initial(array) {
+      return dropRight(array, 1);
+    }
+
+    /**
+     * Creates an array of unique values that are included in all given arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. The order of result values is determined by the
+     * order they occur in the first array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersection([2, 1], [4, 2], [1, 2]);
+     * // => [2]
+     */
+    var intersection = rest(function(arrays) {
+      var mapped = arrayMap(arrays, castArrayLikeObject);
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped)
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `iteratee`
+     * which is invoked for each element of each `arrays` to generate the criterion
+     * by which they're compared. Result values are chosen from the first array.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+     * // => [2.1]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }]
+     */
+    var intersectionBy = rest(function(arrays) {
+      var iteratee = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      if (iteratee === last(mapped)) {
+        iteratee = undefined;
+      } else {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, getIteratee(iteratee))
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `comparator`
+     * which is invoked to compare elements of `arrays`. Result values are chosen
+     * from the first array. The comparator is invoked with two arguments:
+     * (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.intersectionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }]
+     */
+    var intersectionWith = rest(function(arrays) {
+      var comparator = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      if (comparator === last(mapped)) {
+        comparator = undefined;
+      } else {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Converts all elements in `array` into a string separated by `separator`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to convert.
+     * @param {string} [separator=','] The element separator.
+     * @returns {string} Returns the joined string.
+     * @example
+     *
+     * _.join(['a', 'b', 'c'], '~');
+     * // => 'a~b~c'
+     */
+    function join(array, separator) {
+      return array ? nativeJoin.call(array, separator) : '';
+    }
+
+    /**
+     * Gets the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the last element of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     */
+    function last(array) {
+      var length = array ? array.length : 0;
+      return length ? array[length - 1] : undefined;
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it iterates over elements of
+     * `array` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} 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, else `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 1, 2], 2);
+     * // => 3
+     *
+     * // Search from the `fromIndex`.
+     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return -1;
+      }
+      var index = length;
+      if (fromIndex !== undefined) {
+        index = toInteger(fromIndex);
+        index = (
+          index < 0
+            ? nativeMax(length + index, 0)
+            : nativeMin(index, length - 1)
+        ) + 1;
+      }
+      if (value !== value) {
+        return indexOfNaN(array, index, true);
+      }
+      while (index--) {
+        if (array[index] === value) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Gets the nth element of `array`. If `n` is negative, the nth element
+     * from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.11.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=0] The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'd'];
+     *
+     * _.nth(array, 1);
+     * // => 'b'
+     *
+     * _.nth(array, -2);
+     * // => 'c';
+     */
+    function nth(array, n) {
+      return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
+    }
+
+    /**
+     * Removes all given values from `array` using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+     * to remove elements from an array by predicate.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...*} [values] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     *
+     * _.pull(array, 2, 3);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    var pull = rest(pullAll);
+
+    /**
+     * This method is like `_.pull` except that it accepts an array of values to remove.
+     *
+     * **Note:** Unlike `_.difference`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     *
+     * _.pullAll(array, [2, 3]);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    function pullAll(array, values) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values)
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `iteratee` which is
+     * invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. The iteratee is invoked with one argument: (value).
+     *
+     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+     *
+     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+     * console.log(array);
+     * // => [{ 'x': 2 }]
+     */
+    function pullAllBy(array, values, iteratee) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, getIteratee(iteratee))
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `comparator` which
+     * is invoked to compare elements of `array` to `values`. The comparator is
+     * invoked with two arguments: (arrVal, othVal).
+     *
+     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+     *
+     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+     * console.log(array);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+     */
+    function pullAllWith(array, values, comparator) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, undefined, comparator)
+        : array;
+    }
+
+    /**
+     * Removes elements from `array` corresponding to `indexes` and returns an
+     * array of removed elements.
+     *
+     * **Note:** Unlike `_.at`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [5, 10, 15, 20];
+     * var evens = _.pullAt(array, 1, 3);
+     *
+     * console.log(array);
+     * // => [5, 15]
+     *
+     * console.log(evens);
+     * // => [10, 20]
+     */
+    var pullAt = rest(function(array, indexes) {
+      indexes = baseFlatten(indexes, 1);
+
+      var length = array ? array.length : 0,
+          result = baseAt(array, indexes);
+
+      basePullAt(array, arrayMap(indexes, function(index) {
+        return isIndex(index, length) ? +index : index;
+      }).sort(compareAscending));
+
+      return result;
+    });
+
+    /**
+     * Removes all elements from `array` that `predicate` returns truthy for
+     * and returns an array of the removed elements. The predicate is invoked
+     * with three arguments: (value, index, array).
+     *
+     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+     * to pull elements from an array by value.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4];
+     * var evens = _.remove(array, function(n) {
+     *   return n % 2 == 0;
+     * });
+     *
+     * console.log(array);
+     * // => [1, 3]
+     *
+     * console.log(evens);
+     * // => [2, 4]
+     */
+    function remove(array, predicate) {
+      var result = [];
+      if (!(array && array.length)) {
+        return result;
+      }
+      var index = -1,
+          indexes = [],
+          length = array.length;
+
+      predicate = getIteratee(predicate, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (predicate(value, index, array)) {
+          result.push(value);
+          indexes.push(index);
+        }
+      }
+      basePullAt(array, indexes);
+      return result;
+    }
+
+    /**
+     * Reverses `array` so that the first element becomes the last, the second
+     * element becomes the second to last, and so on.
+     *
+     * **Note:** This method mutates `array` and is based on
+     * [`Array#reverse`](https://mdn.io/Array/reverse).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.reverse(array);
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function reverse(array) {
+      return array ? nativeReverse.call(array) : array;
+    }
+
+    /**
+     * Creates a slice of `array` from `start` up to, but not including, `end`.
+     *
+     * **Note:** This method is used instead of
+     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+     * returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function slice(array, start, end) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+        start = 0;
+        end = length;
+      }
+      else {
+        start = start == null ? 0 : toInteger(start);
+        end = end === undefined ? length : toInteger(end);
+      }
+      return baseSlice(array, start, end);
+    }
+
+    /**
+     * Uses a binary search to determine the lowest index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([30, 50], 40);
+     * // => 1
+     *
+     * _.sortedIndex([4, 5], 4);
+     * // => 0
+     */
+    function sortedIndex(array, value) {
+      return baseSortedIndex(array, value);
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };
+     *
+     * _.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));
+     * // => 1
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+     * // => 0
+     */
+    function sortedIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee));
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedIndexOf([1, 1, 2, 2], 2);
+     * // => 2
+     */
+    function sortedIndexOf(array, value) {
+      var length = array ? array.length : 0;
+      if (length) {
+        var index = baseSortedIndex(array, value);
+        if (index < length && eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it returns the highest
+     * index at which `value` should be inserted into `array` in order to
+     * maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedLastIndex([4, 5], 4);
+     * // => 1
+     */
+    function sortedLastIndex(array, value) {
+      return baseSortedIndex(array, value, true);
+    }
+
+    /**
+     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+     * // => 1
+     */
+    function sortedLastIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee), true);
+    }
+
+    /**
+     * This method is like `_.lastIndexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedLastIndexOf([1, 1, 2, 2], 2);
+     * // => 3
+     */
+    function sortedLastIndexOf(array, value) {
+      var length = array ? array.length : 0;
+      if (length) {
+        var index = baseSortedIndex(array, value, true) - 1;
+        if (eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.uniq` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniq([1, 1, 2]);
+     * // => [1, 2]
+     */
+    function sortedUniq(array) {
+      return (array && array.length)
+        ? baseSortedUniq(array)
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniqBy` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+     * // => [1.1, 2.3]
+     */
+    function sortedUniqBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSortedUniq(array, getIteratee(iteratee))
+        : [];
+    }
+
+    /**
+     * Gets all but the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.tail([1, 2, 3]);
+     * // => [2, 3]
+     */
+    function tail(array) {
+      return drop(array, 1);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.take([1, 2, 3]);
+     * // => [1]
+     *
+     * _.take([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.take([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.take([1, 2, 3], 0);
+     * // => []
+     */
+    function take(array, n, guard) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeRight([1, 2, 3]);
+     * // => [3]
+     *
+     * _.takeRight([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.takeRight([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.takeRight([1, 2, 3], 0);
+     * // => []
+     */
+    function takeRight(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the end. Elements are
+     * taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.takeRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeRightWhile(users, ['active', false]);
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeRightWhile(users, 'active');
+     * // => []
+     */
+    function takeRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), false, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the beginning. Elements
+     * are taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false},
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.takeWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeWhile(users, ['active', false]);
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeWhile(users, 'active');
+     * // => []
+     */
+    function takeWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3))
+        : [];
+    }
+
+    /**
+     * Creates an array of unique values, in order, from all given arrays using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.union([2, 1], [4, 2], [1, 2]);
+     * // => [2, 1, 4]
+     */
+    var union = rest(function(arrays) {
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which uniqueness is computed. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.unionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+     * // => [2.1, 1.2, 4.3]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    var unionBy = rest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `comparator` which
+     * is invoked to compare elements of `arrays`. The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.unionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var unionWith = rest(function(arrays) {
+      var comparator = last(arrays);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
+    });
+
+    /**
+     * Creates a duplicate-free version of an array, using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons, in which only the first occurrence of each
+     * element is kept.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniq([2, 1, 2]);
+     * // => [2, 1]
+     */
+    function uniq(array) {
+      return (array && array.length)
+        ? baseUniq(array)
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * uniqueness is computed. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+     * // => [2.1, 1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniqBy(array, iteratee) {
+      return (array && array.length)
+        ? baseUniq(array, getIteratee(iteratee))
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `comparator` which
+     * is invoked to compare elements of `array`. The comparator is invoked with
+     * two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 },  { 'x': 1, 'y': 2 }];
+     *
+     * _.uniqWith(objects, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+     */
+    function uniqWith(array, comparator) {
+      return (array && array.length)
+        ? baseUniq(array, undefined, comparator)
+        : [];
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts an array of grouped
+     * elements and creates an array regrouping the elements to their pre-zip
+     * configuration.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.2.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     *
+     * _.unzip(zipped);
+     * // => [['fred', 'barney'], [30, 40], [true, false]]
+     */
+    function unzip(array) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var length = 0;
+      array = arrayFilter(array, function(group) {
+        if (isArrayLikeObject(group)) {
+          length = nativeMax(group.length, length);
+          return true;
+        }
+      });
+      return baseTimes(length, function(index) {
+        return arrayMap(array, baseProperty(index));
+      });
+    }
+
+    /**
+     * This method is like `_.unzip` except that it accepts `iteratee` to specify
+     * how regrouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @param {Function} [iteratee=_.identity] The function to combine
+     *  regrouped values.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+     * // => [[1, 10, 100], [2, 20, 200]]
+     *
+     * _.unzipWith(zipped, _.add);
+     * // => [3, 30, 300]
+     */
+    function unzipWith(array, iteratee) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var result = unzip(array);
+      if (iteratee == null) {
+        return result;
+      }
+      return arrayMap(result, function(group) {
+        return apply(iteratee, undefined, group);
+      });
+    }
+
+    /**
+     * Creates an array excluding all given values using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to filter.
+     * @param {...*} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.difference, _.xor
+     * @example
+     *
+     * _.without([1, 2, 1, 3], 1, 2);
+     * // => [3]
+     */
+    var without = rest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, values)
+        : [];
+    });
+
+    /**
+     * Creates an array of unique values that is the
+     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+     * of the given arrays. The order of result values is determined by the order
+     * they occur in the arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of values.
+     * @see _.difference, _.without
+     * @example
+     *
+     * _.xor([2, 1], [4, 2]);
+     * // => [1, 4]
+     */
+    var xor = rest(function(arrays) {
+      return baseXor(arrayFilter(arrays, isArrayLikeObject));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which by which they're compared. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of values.
+     * @example
+     *
+     * _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+     * // => [1.2, 4.3]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var xorBy = rest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `comparator` which is
+     * invoked to compare elements of `arrays`. The comparator is invoked with
+     * two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.xorWith(objects, others, _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var xorWith = rest(function(arrays) {
+      var comparator = last(arrays);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
+    });
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the
+     * first elements of the given arrays, the second of which contains the
+     * second elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     */
+    var zip = rest(unzip);
+
+    /**
+     * This method is like `_.fromPairs` except that it accepts two arrays,
+     * one of property identifiers and one of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.4.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObject(['a', 'b'], [1, 2]);
+     * // => { 'a': 1, 'b': 2 }
+     */
+    function zipObject(props, values) {
+      return baseZipObject(props || [], values || [], assignValue);
+    }
+
+    /**
+     * This method is like `_.zipObject` except that it supports property paths.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+     */
+    function zipObjectDeep(props, values) {
+      return baseZipObject(props || [], values || [], baseSet);
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts `iteratee` to specify
+     * how grouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @param {Function} [iteratee=_.identity] The function to combine grouped values.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+     *   return a + b + c;
+     * });
+     * // => [111, 222]
+     */
+    var zipWith = rest(function(arrays) {
+      var length = arrays.length,
+          iteratee = length > 1 ? arrays[length - 1] : undefined;
+
+      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
+      return unzipWith(arrays, iteratee);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+     * chain sequences enabled. The result of such sequences must be unwrapped
+     * with `_#value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Seq
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36 },
+     *   { 'user': 'fred',    'age': 40 },
+     *   { 'user': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _
+     *   .chain(users)
+     *   .sortBy('age')
+     *   .map(function(o) {
+     *     return o.user + ' is ' + o.age;
+     *   })
+     *   .head()
+     *   .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      var result = lodash(value);
+      result.__chain__ = true;
+      return result;
+    }
+
+    /**
+     * This method invokes `interceptor` and returns `value`. The interceptor
+     * is invoked with one argument; (value). The purpose of this method is to
+     * "tap into" a method chain sequence in order to modify intermediate results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3])
+     *  .tap(function(array) {
+     *    // Mutate input array.
+     *    array.pop();
+     *  })
+     *  .reverse()
+     *  .value();
+     * // => [2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * This method is like `_.tap` except that it returns the result of `interceptor`.
+     * The purpose of this method is to "pass thru" values replacing intermediate
+     * results in a method chain sequence.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns the result of `interceptor`.
+     * @example
+     *
+     * _('  abc  ')
+     *  .chain()
+     *  .trim()
+     *  .thru(function(value) {
+     *    return [value];
+     *  })
+     *  .value();
+     * // => ['abc']
+     */
+    function thru(value, interceptor) {
+      return interceptor(value);
+    }
+
+    /**
+     * This method is the wrapper version of `_.at`.
+     *
+     * @name at
+     * @memberOf _
+     * @since 1.0.0
+     * @category Seq
+     * @param {...(string|string[])} [paths] The property paths of elements to pick.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _(object).at(['a[0].b.c', 'a[1]']).value();
+     * // => [3, 4]
+     *
+     * _(['a', 'b', 'c']).at(0, 2).value();
+     * // => ['a', 'c']
+     */
+    var wrapperAt = rest(function(paths) {
+      paths = baseFlatten(paths, 1);
+      var length = paths.length,
+          start = length ? paths[0] : 0,
+          value = this.__wrapped__,
+          interceptor = function(object) { return baseAt(object, paths); };
+
+      if (length > 1 || this.__actions__.length ||
+          !(value instanceof LazyWrapper) || !isIndex(start)) {
+        return this.thru(interceptor);
+      }
+      value = value.slice(start, +start + (length ? 1 : 0));
+      value.__actions__.push({
+        'func': thru,
+        'args': [interceptor],
+        'thisArg': undefined
+      });
+      return new LodashWrapper(value, this.__chain__).thru(function(array) {
+        if (length && !array.length) {
+          array.push(undefined);
+        }
+        return array;
+      });
+    });
+
+    /**
+     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+     *
+     * @name chain
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * // A sequence without explicit chaining.
+     * _(users).head();
+     * // => { 'user': 'barney', 'age': 36 }
+     *
+     * // A sequence with explicit chaining.
+     * _(users)
+     *   .chain()
+     *   .head()
+     *   .pick('user')
+     *   .value();
+     * // => { 'user': 'barney' }
+     */
+    function wrapperChain() {
+      return chain(this);
+    }
+
+    /**
+     * Executes the chain sequence and returns the wrapped result.
+     *
+     * @name commit
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2];
+     * var wrapped = _(array).push(3);
+     *
+     * console.log(array);
+     * // => [1, 2]
+     *
+     * wrapped = wrapped.commit();
+     * console.log(array);
+     * // => [1, 2, 3]
+     *
+     * wrapped.last();
+     * // => 3
+     *
+     * console.log(array);
+     * // => [1, 2, 3]
+     */
+    function wrapperCommit() {
+      return new LodashWrapper(this.value(), this.__chain__);
+    }
+
+    /**
+     * Gets the next value on a wrapped object following the
+     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+     *
+     * @name next
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the next iterator value.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 1 }
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 2 }
+     *
+     * wrapped.next();
+     * // => { 'done': true, 'value': undefined }
+     */
+    function wrapperNext() {
+      if (this.__values__ === undefined) {
+        this.__values__ = toArray(this.value());
+      }
+      var done = this.__index__ >= this.__values__.length,
+          value = done ? undefined : this.__values__[this.__index__++];
+
+      return { 'done': done, 'value': value };
+    }
+
+    /**
+     * Enables the wrapper to be iterable.
+     *
+     * @name Symbol.iterator
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped[Symbol.iterator]() === wrapped;
+     * // => true
+     *
+     * Array.from(wrapped);
+     * // => [1, 2]
+     */
+    function wrapperToIterator() {
+      return this;
+    }
+
+    /**
+     * Creates a clone of the chain sequence planting `value` as the wrapped value.
+     *
+     * @name plant
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @param {*} value The value to plant.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2]).map(square);
+     * var other = wrapped.plant([3, 4]);
+     *
+     * other.value();
+     * // => [9, 16]
+     *
+     * wrapped.value();
+     * // => [1, 4]
+     */
+    function wrapperPlant(value) {
+      var result,
+          parent = this;
+
+      while (parent instanceof baseLodash) {
+        var clone = wrapperClone(parent);
+        clone.__index__ = 0;
+        clone.__values__ = undefined;
+        if (result) {
+          previous.__wrapped__ = clone;
+        } else {
+          result = clone;
+        }
+        var previous = clone;
+        parent = parent.__wrapped__;
+      }
+      previous.__wrapped__ = value;
+      return result;
+    }
+
+    /**
+     * This method is the wrapper version of `_.reverse`.
+     *
+     * **Note:** This method mutates the wrapped array.
+     *
+     * @name reverse
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _(array).reverse().value()
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function wrapperReverse() {
+      var value = this.__wrapped__;
+      if (value instanceof LazyWrapper) {
+        var wrapped = value;
+        if (this.__actions__.length) {
+          wrapped = new LazyWrapper(this);
+        }
+        wrapped = wrapped.reverse();
+        wrapped.__actions__.push({
+          'func': thru,
+          'args': [reverse],
+          'thisArg': undefined
+        });
+        return new LodashWrapper(wrapped, this.__chain__);
+      }
+      return this.thru(reverse);
+    }
+
+    /**
+     * Executes the chain sequence to resolve the unwrapped value.
+     *
+     * @name value
+     * @memberOf _
+     * @since 0.1.0
+     * @alias toJSON, valueOf
+     * @category Seq
+     * @returns {*} Returns the resolved unwrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).value();
+     * // => [1, 2, 3]
+     */
+    function wrapperValue() {
+      return baseWrapperValue(this.__wrapped__, this.__actions__);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the number of times the key was returned by `iteratee`. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1);
+    });
+
+    /**
+     * Checks if `predicate` returns truthy for **all** elements of `collection`.
+     * Iteration is stopped once `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes'], Boolean);
+     * // => false
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.every(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.every(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.every(users, 'active');
+     * // => false
+     */
+    function every(collection, predicate, guard) {
+      var func = isArray(collection) ? arrayEvery : baseEvery;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning an array of all elements
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.reject
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, { 'age': 36, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.filter(users, 'active');
+     * // => objects for ['barney']
+     */
+    function filter(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning the first element
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': true },
+     *   { 'user': 'fred',    'age': 40, 'active': false },
+     *   { 'user': 'pebbles', 'age': 1,  'active': true }
+     * ];
+     *
+     * _.find(users, function(o) { return o.age < 40; });
+     * // => object for 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.find(users, { 'age': 1, 'active': true });
+     * // => object for 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.find(users, ['active', false]);
+     * // => object for 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.find(users, 'active');
+     * // => object for 'barney'
+     */
+    function find(collection, predicate) {
+      predicate = getIteratee(predicate, 3);
+      if (isArray(collection)) {
+        var index = baseFindIndex(collection, predicate);
+        return index > -1 ? collection[index] : undefined;
+      }
+      return baseFind(collection, predicate, baseEach);
+    }
+
+    /**
+     * This method is like `_.find` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(n) {
+     *   return n % 2 == 1;
+     * });
+     * // => 3
+     */
+    function findLast(collection, predicate) {
+      predicate = getIteratee(predicate, 3);
+      if (isArray(collection)) {
+        var index = baseFindIndex(collection, predicate, true);
+        return index > -1 ? collection[index] : undefined;
+      }
+      return baseFind(collection, predicate, baseEachRight);
+    }
+
+    /**
+     * Creates a flattened array of values by running each element in `collection`
+     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
+     * with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [n, n];
+     * }
+     *
+     * _.flatMap([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMap(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), 1);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDeep([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMapDeep(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), INFINITY);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDepth([1, 2], duplicate, 2);
+     * // => [[1, 1], [2, 2]]
+     */
+    function flatMapDepth(collection, iteratee, depth) {
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(map(collection, iteratee), depth);
+    }
+
+    /**
+     * Iterates over elements of `collection` and invokes `iteratee` for each element.
+     * The iteratee is invoked with three arguments: (value, index|key, collection).
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * **Note:** As with other "Collections" methods, objects with a "length"
+     * property are iterated like arrays. To avoid this behavior use `_.forIn`
+     * or `_.forOwn` for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias each
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEachRight
+     * @example
+     *
+     * _([1, 2]).forEach(function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `1` then `2`.
+     *
+     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forEach(collection, iteratee) {
+      return (typeof iteratee == 'function' && isArray(collection))
+        ? arrayEach(collection, iteratee)
+        : baseEach(collection, getIteratee(iteratee));
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @alias eachRight
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEach
+     * @example
+     *
+     * _.forEachRight([1, 2], function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `2` then `1`.
+     */
+    function forEachRight(collection, iteratee) {
+      return (typeof iteratee == 'function' && isArray(collection))
+        ? arrayEachRight(collection, iteratee)
+        : baseEachRight(collection, getIteratee(iteratee));
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The order of grouped values
+     * is determined by the order they occur in `collection`. The corresponding
+     * value of each key is an array of elements responsible for generating the
+     * key. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': [4.2], '6': [6.1, 6.3] }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        result[key].push(value);
+      } else {
+        result[key] = [value];
+      }
+    });
+
+    /**
+     * Checks if `value` is in `collection`. If `collection` is a string, it's
+     * checked for a substring of `value`, otherwise
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * is used for equality comparisons. If `fromIndex` is negative, it's used as
+     * the offset from the end of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {boolean} Returns `true` if `value` is found, else `false`.
+     * @example
+     *
+     * _.includes([1, 2, 3], 1);
+     * // => true
+     *
+     * _.includes([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');
+     * // => true
+     *
+     * _.includes('pebbles', 'eb');
+     * // => true
+     */
+    function includes(collection, value, fromIndex, guard) {
+      collection = isArrayLike(collection) ? collection : values(collection);
+      fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
+
+      var length = collection.length;
+      if (fromIndex < 0) {
+        fromIndex = nativeMax(length + fromIndex, 0);
+      }
+      return isString(collection)
+        ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
+        : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
+    }
+
+    /**
+     * Invokes the method at `path` of each element in `collection`, returning
+     * an array of the results of each invoked method. Any additional arguments
+     * are provided to each invoked method. If `methodName` is a function, it's
+     * invoked for and `this` bound to, each element in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|string} path The path of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [args] The arguments to invoke each method with.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invokeMap([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    var invokeMap = rest(function(collection, path, args) {
+      var index = -1,
+          isFunc = typeof path == 'function',
+          isProp = isKey(path),
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value) {
+        var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
+        result[++index] = func ? apply(func, value, args) : baseInvoke(value, path, args);
+      });
+      return result;
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the last element responsible for generating the key. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var array = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.keyBy(array, function(o) {
+     *   return String.fromCharCode(o.code);
+     * });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.keyBy(array, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     */
+    var keyBy = createAggregator(function(result, value, key) {
+      result[key] = value;
+    });
+
+    /**
+     * Creates an array of values by running each element in `collection` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+     *
+     * The guarded methods are:
+     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * _.map([4, 8], square);
+     * // => [16, 64]
+     *
+     * _.map({ 'a': 4, 'b': 8 }, square);
+     * // => [16, 64] (iteration order is not guaranteed)
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, 'user');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, iteratee) {
+      var func = isArray(collection) ? arrayMap : baseMap;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.sortBy` except that it allows specifying the sort
+     * orders of the iteratees to sort by. If `orders` is unspecified, all values
+     * are sorted in ascending order. Otherwise, specify an order of "desc" for
+     * descending or "asc" for ascending sort order of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
+     *  The iteratees to sort by.
+     * @param {string[]} [orders] The sort orders of `iteratees`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 34 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 36 }
+     * ];
+     *
+     * // Sort by `user` in ascending order and by `age` in descending order.
+     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     */
+    function orderBy(collection, iteratees, orders, guard) {
+      if (collection == null) {
+        return [];
+      }
+      if (!isArray(iteratees)) {
+        iteratees = iteratees == null ? [] : [iteratees];
+      }
+      orders = guard ? undefined : orders;
+      if (!isArray(orders)) {
+        orders = orders == null ? [] : [orders];
+      }
+      return baseOrderBy(collection, iteratees, orders);
+    }
+
+    /**
+     * Creates an array of elements split into two groups, the first of which
+     * contains elements `predicate` returns truthy for, the second of which
+     * contains elements `predicate` returns falsey for. The predicate is
+     * invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the array of grouped elements.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': false },
+     *   { 'user': 'fred',    'age': 40, 'active': true },
+     *   { 'user': 'pebbles', 'age': 1,  'active': false }
+     * ];
+     *
+     * _.partition(users, function(o) { return o.active; });
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.partition(users, { 'age': 1, 'active': false });
+     * // => objects for [['pebbles'], ['barney', 'fred']]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.partition(users, ['active', false]);
+     * // => objects for [['barney', 'pebbles'], ['fred']]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.partition(users, 'active');
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     */
+    var partition = createAggregator(function(result, value, key) {
+      result[key ? 0 : 1].push(value);
+    }, function() { return [[], []]; });
+
+    /**
+     * Reduces `collection` to a value which is the accumulated result of running
+     * each element in `collection` thru `iteratee`, where each successive
+     * invocation is supplied the return value of the previous. If `accumulator`
+     * is not given, the first element of `collection` is used as the initial
+     * value. The iteratee is invoked with four arguments:
+     * (accumulator, value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.reduce`, `_.reduceRight`, and `_.transform`.
+     *
+     * The guarded methods are:
+     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+     * and `sortBy`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduceRight
+     * @example
+     *
+     * _.reduce([1, 2], function(sum, n) {
+     *   return sum + n;
+     * }, 0);
+     * // => 3
+     *
+     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     *   return result;
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+     */
+    function reduce(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduce : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduce
+     * @example
+     *
+     * var array = [[0, 1], [2, 3], [4, 5]];
+     *
+     * _.reduceRight(array, function(flattened, other) {
+     *   return flattened.concat(other);
+     * }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduceRight : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
+    }
+
+    /**
+     * The opposite of `_.filter`; this method returns the elements of `collection`
+     * that `predicate` does **not** return truthy for.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.filter
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': true }
+     * ];
+     *
+     * _.reject(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.reject(users, { 'age': 40, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.reject(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.reject(users, 'active');
+     * // => objects for ['barney']
+     */
+    function reject(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      predicate = getIteratee(predicate, 3);
+      return func(collection, function(value, index, collection) {
+        return !predicate(value, index, collection);
+      });
+    }
+
+    /**
+     * Gets a random element from `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @returns {*} Returns the random element.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     */
+    function sample(collection) {
+      var array = isArrayLike(collection) ? collection : values(collection),
+          length = array.length;
+
+      return length > 0 ? array[baseRandom(0, length - 1)] : undefined;
+    }
+
+    /**
+     * Gets `n` random elements at unique keys from `collection` up to the
+     * size of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @param {number} [n=1] The number of elements to sample.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the random elements.
+     * @example
+     *
+     * _.sampleSize([1, 2, 3], 2);
+     * // => [3, 1]
+     *
+     * _.sampleSize([1, 2, 3], 4);
+     * // => [2, 3, 1]
+     */
+    function sampleSize(collection, n, guard) {
+      var index = -1,
+          result = toArray(collection),
+          length = result.length,
+          lastIndex = length - 1;
+
+      if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = baseClamp(toInteger(n), 0, length);
+      }
+      while (++index < n) {
+        var rand = baseRandom(index, lastIndex),
+            value = result[rand];
+
+        result[rand] = result[index];
+        result[index] = value;
+      }
+      result.length = n;
+      return result;
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the
+     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4]);
+     * // => [4, 1, 3, 2]
+     */
+    function shuffle(collection) {
+      return sampleSize(collection, MAX_ARRAY_LENGTH);
+    }
+
+    /**
+     * Gets the size of `collection` by returning its length for array-like
+     * values or the number of own enumerable string keyed properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to inspect.
+     * @returns {number} Returns the collection size.
+     * @example
+     *
+     * _.size([1, 2, 3]);
+     * // => 3
+     *
+     * _.size({ 'a': 1, 'b': 2 });
+     * // => 2
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      if (collection == null) {
+        return 0;
+      }
+      if (isArrayLike(collection)) {
+        var result = collection.length;
+        return (result && isString(collection)) ? stringSize(collection) : result;
+      }
+      if (isObjectLike(collection)) {
+        var tag = getTag(collection);
+        if (tag == mapTag || tag == setTag) {
+          return collection.size;
+        }
+      }
+      return keys(collection).length;
+    }
+
+    /**
+     * Checks if `predicate` returns truthy for **any** element of `collection`.
+     * Iteration is stopped once `predicate` returns truthy. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var users = [
+     *   { 'user': 'barney', 'active': true },
+     *   { 'user': 'fred',   'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.some(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.some(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.some(users, 'active');
+     * // => true
+     */
+    function some(collection, predicate, guard) {
+      var func = isArray(collection) ? arraySome : baseSome;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection thru each iteratee. This method
+     * performs a stable sort, that is, it preserves the original sort order of
+     * equal elements. The iteratees are invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [iteratees=[_.identity]] The iteratees to sort by.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 34 }
+     * ];
+     *
+     * _.sortBy(users, function(o) { return o.user; });
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     *
+     * _.sortBy(users, ['user', 'age']);
+     * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+     *
+     * _.sortBy(users, 'user', function(o) {
+     *   return Math.floor(o.age / 10);
+     * });
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     */
+    var sortBy = rest(function(collection, iteratees) {
+      if (collection == null) {
+        return [];
+      }
+      var length = iteratees.length;
+      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+        iteratees = [];
+      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+        iteratees = [iteratees[0]];
+      }
+      iteratees = (iteratees.length == 1 && isArray(iteratees[0]))
+        ? iteratees[0]
+        : baseFlatten(iteratees, 1, isFlattenableIteratee);
+
+      return baseOrderBy(collection, iteratees, []);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the timestamp of the number of milliseconds that have elapsed since
+     * the Unix epoch (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @type {Function}
+     * @category Date
+     * @returns {number} Returns the timestamp.
+     * @example
+     *
+     * _.defer(function(stamp) {
+     *   console.log(_.now() - stamp);
+     * }, _.now());
+     * // => Logs the number of milliseconds it took for the deferred function to be invoked.
+     */
+    var now = Date.now;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The opposite of `_.before`; this method creates a function that invokes
+     * `func` once it's called `n` or more times.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {number} n The number of calls before `func` is invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => Logs 'done saving!' after the two async saves have completed.
+     */
+    function after(n, func) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func`, with up to `n` arguments,
+     * ignoring any additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @param {number} [n=func.length] The arity cap.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+     * // => [6, 8, 10]
+     */
+    function ary(func, n, guard) {
+      n = guard ? undefined : n;
+      n = (func && n == null) ? func.length : n;
+      return createWrapper(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
+    }
+
+    /**
+     * Creates a function that invokes `func`, with the `this` binding and arguments
+     * of the created function, while it's called less than `n` times. Subsequent
+     * calls to the created function return the result of the last `func` invocation.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {number} n The number of calls at which `func` is no longer invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * jQuery(element).on('click', _.before(5, addContactToList));
+     * // => allows adding up to 4 contacts to the list
+     */
+    function before(n, func) {
+      var result;
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n > 0) {
+          result = func.apply(this, arguments);
+        }
+        if (n <= 1) {
+          func = undefined;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of `thisArg`
+     * and `partials` prepended to the arguments it receives.
+     *
+     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** Unlike native `Function#bind` this method doesn't set the "length"
+     * property of bound functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to bind.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var greet = function(greeting, punctuation) {
+     *   return greeting + ' ' + this.user + punctuation;
+     * };
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * var bound = _.bind(greet, object, 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bind(greet, object, _, '!');
+     * bound('hi');
+     * // => 'hi fred!'
+     */
+    var bind = rest(function(func, thisArg, partials) {
+      var bitmask = BIND_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getPlaceholder(bind));
+        bitmask |= PARTIAL_FLAG;
+      }
+      return createWrapper(func, bitmask, thisArg, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes the method at `object[key]` with `partials`
+     * prepended to the arguments it receives.
+     *
+     * This method differs from `_.bind` by allowing bound functions to reference
+     * methods that may be redefined or don't yet exist. See
+     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+     * for more details.
+     *
+     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Function
+     * @param {Object} object The object to invoke the method on.
+     * @param {string} key The key of the method.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'user': 'fred',
+     *   'greet': function(greeting, punctuation) {
+     *     return greeting + ' ' + this.user + punctuation;
+     *   }
+     * };
+     *
+     * var bound = _.bindKey(object, 'greet', 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * object.greet = function(greeting, punctuation) {
+     *   return greeting + 'ya ' + this.user + punctuation;
+     * };
+     *
+     * bound('!');
+     * // => 'hiya fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bindKey(object, 'greet', _, '!');
+     * bound('hi');
+     * // => 'hiya fred!'
+     */
+    var bindKey = rest(function(object, key, partials) {
+      var bitmask = BIND_FLAG | BIND_KEY_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getPlaceholder(bindKey));
+        bitmask |= PARTIAL_FLAG;
+      }
+      return createWrapper(key, bitmask, object, partials, holders);
+    });
+
+    /**
+     * Creates a function that accepts arguments of `func` and either invokes
+     * `func` returning its result, if at least `arity` number of arguments have
+     * been provided, or returns a function that accepts the remaining `func`
+     * arguments, and so on. The arity of `func` may be specified if `func.length`
+     * is not sufficient.
+     *
+     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curry(abc);
+     *
+     * curried(1)(2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(1)(_, 3)(2);
+     * // => [1, 2, 3]
+     */
+    function curry(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrapper(func, CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curry.placeholder;
+      return result;
+    }
+
+    /**
+     * This method is like `_.curry` except that arguments are applied to `func`
+     * in the manner of `_.partialRight` instead of `_.partial`.
+     *
+     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curryRight(abc);
+     *
+     * curried(3)(2)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(2, 3)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(3)(1, _)(2);
+     * // => [1, 2, 3]
+     */
+    function curryRight(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrapper(func, CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curryRight.placeholder;
+      return result;
+    }
+
+    /**
+     * Creates a debounced function that delays invoking `func` until after `wait`
+     * milliseconds have elapsed since the last time the debounced function was
+     * invoked. The debounced function comes with a `cancel` method to cancel
+     * delayed `func` invocations and a `flush` method to immediately invoke them.
+     * Provide an options object to indicate whether `func` should be invoked on
+     * the leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+     * with the last arguments provided to the debounced function. Subsequent calls
+     * to the debounced function return the result of the last `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+     * on the trailing edge of the timeout only if the debounced function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.debounce` and `_.throttle`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to debounce.
+     * @param {number} [wait=0] The number of milliseconds to delay.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=false]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {number} [options.maxWait]
+     *  The maximum time `func` is allowed to be delayed before it's invoked.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // Avoid costly calculations while the window size is in flux.
+     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+     *
+     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+     * jQuery(element).on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * }));
+     *
+     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+     * var source = new EventSource('/stream');
+     * jQuery(source).on('message', debounced);
+     *
+     * // Cancel the trailing debounced invocation.
+     * jQuery(window).on('popstate', debounced.cancel);
+     */
+    function debounce(func, wait, options) {
+      var lastArgs,
+          lastThis,
+          maxWait,
+          result,
+          timerId,
+          lastCallTime = 0,
+          lastInvokeTime = 0,
+          leading = false,
+          maxing = false,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      wait = toNumber(wait) || 0;
+      if (isObject(options)) {
+        leading = !!options.leading;
+        maxing = 'maxWait' in options;
+        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+
+      function invokeFunc(time) {
+        var args = lastArgs,
+            thisArg = lastThis;
+
+        lastArgs = lastThis = undefined;
+        lastInvokeTime = time;
+        result = func.apply(thisArg, args);
+        return result;
+      }
+
+      function leadingEdge(time) {
+        // Reset any `maxWait` timer.
+        lastInvokeTime = time;
+        // Start the timer for the trailing edge.
+        timerId = setTimeout(timerExpired, wait);
+        // Invoke the leading edge.
+        return leading ? invokeFunc(time) : result;
+      }
+
+      function remainingWait(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime,
+            result = wait - timeSinceLastCall;
+
+        return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
+      }
+
+      function shouldInvoke(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime;
+
+        // Either this is the first call, activity has stopped and we're at the
+        // trailing edge, the system time has gone backwards and we're treating
+        // it as the trailing edge, or we've hit the `maxWait` limit.
+        return (!lastCallTime || (timeSinceLastCall >= wait) ||
+          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
+      }
+
+      function timerExpired() {
+        var time = now();
+        if (shouldInvoke(time)) {
+          return trailingEdge(time);
+        }
+        // Restart the timer.
+        timerId = setTimeout(timerExpired, remainingWait(time));
+      }
+
+      function trailingEdge(time) {
+        clearTimeout(timerId);
+        timerId = undefined;
+
+        // Only invoke if we have `lastArgs` which means `func` has been
+        // debounced at least once.
+        if (trailing && lastArgs) {
+          return invokeFunc(time);
+        }
+        lastArgs = lastThis = undefined;
+        return result;
+      }
+
+      function cancel() {
+        if (timerId !== undefined) {
+          clearTimeout(timerId);
+        }
+        lastCallTime = lastInvokeTime = 0;
+        lastArgs = lastThis = timerId = undefined;
+      }
+
+      function flush() {
+        return timerId === undefined ? result : trailingEdge(now());
+      }
+
+      function debounced() {
+        var time = now(),
+            isInvoking = shouldInvoke(time);
+
+        lastArgs = arguments;
+        lastThis = this;
+        lastCallTime = time;
+
+        if (isInvoking) {
+          if (timerId === undefined) {
+            return leadingEdge(lastCallTime);
+          }
+          if (maxing) {
+            // Handle invocations in a tight loop.
+            clearTimeout(timerId);
+            timerId = setTimeout(timerExpired, wait);
+            return invokeFunc(lastCallTime);
+          }
+        }
+        if (timerId === undefined) {
+          timerId = setTimeout(timerExpired, wait);
+        }
+        return result;
+      }
+      debounced.cancel = cancel;
+      debounced.flush = flush;
+      return debounced;
+    }
+
+    /**
+     * Defers invoking the `func` until the current call stack has cleared. Any
+     * additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to defer.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) {
+     *   console.log(text);
+     * }, 'deferred');
+     * // => Logs 'deferred' after one or more milliseconds.
+     */
+    var defer = rest(function(func, args) {
+      return baseDelay(func, 1, args);
+    });
+
+    /**
+     * Invokes `func` after `wait` milliseconds. Any additional arguments are
+     * provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) {
+     *   console.log(text);
+     * }, 1000, 'later');
+     * // => Logs 'later' after one second.
+     */
+    var delay = rest(function(func, wait, args) {
+      return baseDelay(func, toNumber(wait) || 0, args);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments reversed.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to flip arguments for.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var flipped = _.flip(function() {
+     *   return _.toArray(arguments);
+     * });
+     *
+     * flipped('a', 'b', 'c', 'd');
+     * // => ['d', 'c', 'b', 'a']
+     */
+    function flip(func) {
+      return createWrapper(func, FLIP_FLAG);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided, it determines the cache key for storing the result based on the
+     * arguments provided to the memoized function. By default, the first argument
+     * provided to the memoized function is used as the map cache key. The `func`
+     * is invoked with the `this` binding of the memoized function.
+     *
+     * **Note:** The cache is exposed as the `cache` property on the memoized
+     * function. Its creation may be customized by replacing the `_.memoize.Cache`
+     * constructor with one whose instances implement the
+     * [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
+     * method interface of `delete`, `get`, `has`, and `set`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] The function to resolve the cache key.
+     * @returns {Function} Returns the new memoizing function.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2 };
+     * var other = { 'c': 3, 'd': 4 };
+     *
+     * var values = _.memoize(_.values);
+     * values(object);
+     * // => [1, 2]
+     *
+     * values(other);
+     * // => [3, 4]
+     *
+     * object.a = 2;
+     * values(object);
+     * // => [1, 2]
+     *
+     * // Modify the result cache.
+     * values.cache.set(object, ['a', 'b']);
+     * values(object);
+     * // => ['a', 'b']
+     *
+     * // Replace `_.memoize.Cache`.
+     * _.memoize.Cache = WeakMap;
+     */
+    function memoize(func, resolver) {
+      if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var memoized = function() {
+        var args = arguments,
+            key = resolver ? resolver.apply(this, args) : args[0],
+            cache = memoized.cache;
+
+        if (cache.has(key)) {
+          return cache.get(key);
+        }
+        var result = func.apply(this, args);
+        memoized.cache = cache.set(key, result);
+        return result;
+      };
+      memoized.cache = new (memoize.Cache || MapCache);
+      return memoized;
+    }
+
+    // Assign cache to `_.memoize`.
+    memoize.Cache = MapCache;
+
+    /**
+     * Creates a function that negates the result of the predicate `func`. The
+     * `func` predicate is invoked with the `this` binding and arguments of the
+     * created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} predicate The predicate to negate.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function isEven(n) {
+     *   return n % 2 == 0;
+     * }
+     *
+     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+     * // => [1, 3, 5]
+     */
+    function negate(predicate) {
+      if (typeof predicate != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return function() {
+        return !predicate.apply(this, arguments);
+      };
+    }
+
+    /**
+     * Creates a function that is restricted to invoking `func` once. Repeat calls
+     * to the function return the value of the first invocation. The `func` is
+     * invoked with the `this` binding and arguments of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // `initialize` invokes `createApplication` once
+     */
+    function once(func) {
+      return before(2, func);
+    }
+
+    /**
+     * Creates a function that invokes `func` with arguments transformed by
+     * corresponding `transforms`.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to wrap.
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [transforms[_.identity]] The functions to transform.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function doubled(n) {
+     *   return n * 2;
+     * }
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var func = _.overArgs(function(x, y) {
+     *   return [x, y];
+     * }, square, doubled);
+     *
+     * func(9, 3);
+     * // => [81, 6]
+     *
+     * func(10, 5);
+     * // => [100, 10]
+     */
+    var overArgs = rest(function(func, transforms) {
+      transforms = (transforms.length == 1 && isArray(transforms[0]))
+        ? arrayMap(transforms[0], baseUnary(getIteratee()))
+        : arrayMap(baseFlatten(transforms, 1, isFlattenableIteratee), baseUnary(getIteratee()));
+
+      var funcsLength = transforms.length;
+      return rest(function(args) {
+        var index = -1,
+            length = nativeMin(args.length, funcsLength);
+
+        while (++index < length) {
+          args[index] = transforms[index].call(this, args[index]);
+        }
+        return apply(func, this, args);
+      });
+    });
+
+    /**
+     * Creates a function that invokes `func` with `partials` prepended to the
+     * arguments it receives. This method is like `_.bind` except it does **not**
+     * alter the `this` binding.
+     *
+     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.2.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) {
+     *   return greeting + ' ' + name;
+     * };
+     *
+     * var sayHelloTo = _.partial(greet, 'hello');
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     *
+     * // Partially applied with placeholders.
+     * var greetFred = _.partial(greet, _, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     */
+    var partial = rest(function(func, partials) {
+      var holders = replaceHolders(partials, getPlaceholder(partial));
+      return createWrapper(func, PARTIAL_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * This method is like `_.partial` except that partially applied arguments
+     * are appended to the arguments it receives.
+     *
+     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) {
+     *   return greeting + ' ' + name;
+     * };
+     *
+     * var greetFred = _.partialRight(greet, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     *
+     * // Partially applied with placeholders.
+     * var sayHelloTo = _.partialRight(greet, 'hello', _);
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     */
+    var partialRight = rest(function(func, partials) {
+      var holders = replaceHolders(partials, getPlaceholder(partialRight));
+      return createWrapper(func, PARTIAL_RIGHT_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments arranged according
+     * to the specified `indexes` where the argument value at the first index is
+     * provided as the first argument, the argument value at the second index is
+     * provided as the second argument, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to rearrange arguments for.
+     * @param {...(number|number[])} indexes The arranged argument indexes.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var rearged = _.rearg(function(a, b, c) {
+     *   return [a, b, c];
+     * }, 2, 0, 1);
+     *
+     * rearged('b', 'c', 'a')
+     * // => ['a', 'b', 'c']
+     */
+    var rearg = rest(function(func, indexes) {
+      return createWrapper(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes, 1));
+    });
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * created function and arguments from `start` and beyond provided as
+     * an array.
+     *
+     * **Note:** This method is based on the
+     * [rest parameter](https://mdn.io/rest_parameters).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.rest(function(what, names) {
+     *   return what + ' ' + _.initial(names).join(', ') +
+     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+     * });
+     *
+     * say('hello', 'fred', 'barney', 'pebbles');
+     * // => 'hello fred, barney, & pebbles'
+     */
+    function rest(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = nativeMax(start === undefined ? (func.length - 1) : toInteger(start), 0);
+      return function() {
+        var args = arguments,
+            index = -1,
+            length = nativeMax(args.length - start, 0),
+            array = Array(length);
+
+        while (++index < length) {
+          array[index] = args[start + index];
+        }
+        switch (start) {
+          case 0: return func.call(this, array);
+          case 1: return func.call(this, args[0], array);
+          case 2: return func.call(this, args[0], args[1], array);
+        }
+        var otherArgs = Array(start + 1);
+        index = -1;
+        while (++index < start) {
+          otherArgs[index] = args[index];
+        }
+        otherArgs[start] = array;
+        return apply(func, this, otherArgs);
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * create function and an array of arguments much like
+     * [`Function#apply`](http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply).
+     *
+     * **Note:** This method is based on the
+     * [spread operator](https://mdn.io/spread_operator).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Function
+     * @param {Function} func The function to spread arguments over.
+     * @param {number} [start=0] The start position of the spread.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.spread(function(who, what) {
+     *   return who + ' says ' + what;
+     * });
+     *
+     * say(['fred', 'hello']);
+     * // => 'fred says hello'
+     *
+     * var numbers = Promise.all([
+     *   Promise.resolve(40),
+     *   Promise.resolve(36)
+     * ]);
+     *
+     * numbers.then(_.spread(function(x, y) {
+     *   return x + y;
+     * }));
+     * // => a Promise of 76
+     */
+    function spread(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = start === undefined ? 0 : nativeMax(toInteger(start), 0);
+      return rest(function(args) {
+        var array = args[start],
+            otherArgs = castSlice(args, 0, start);
+
+        if (array) {
+          arrayPush(otherArgs, array);
+        }
+        return apply(func, this, otherArgs);
+      });
+    }
+
+    /**
+     * Creates a throttled function that only invokes `func` at most once per
+     * every `wait` milliseconds. The throttled function comes with a `cancel`
+     * method to cancel delayed `func` invocations and a `flush` method to
+     * immediately invoke them. Provide an options object to indicate whether
+     * `func` should be invoked on the leading and/or trailing edge of the `wait`
+     * timeout. The `func` is invoked with the last arguments provided to the
+     * throttled function. Subsequent calls to the throttled function return the
+     * result of the last `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is
+     * invoked on the trailing edge of the timeout only if the throttled function
+     * is invoked more than once during the `wait` timeout.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.throttle` and `_.debounce`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to throttle.
+     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=true]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // Avoid excessively updating the position while scrolling.
+     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+     *
+     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+     * jQuery(element).on('click', throttled);
+     *
+     * // Cancel the trailing throttled invocation.
+     * jQuery(window).on('popstate', throttled.cancel);
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      if (isObject(options)) {
+        leading = 'leading' in options ? !!options.leading : leading;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+      return debounce(func, wait, {
+        'leading': leading,
+        'maxWait': wait,
+        'trailing': trailing
+      });
+    }
+
+    /**
+     * Creates a function that accepts up to one argument, ignoring any
+     * additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.unary(parseInt));
+     * // => [6, 8, 10]
+     */
+    function unary(func) {
+      return ary(func, 1);
+    }
+
+    /**
+     * Creates a function that provides `value` to the wrapper function as its
+     * first argument. Any additional arguments provided to the function are
+     * appended to those provided to the wrapper function. The wrapper is invoked
+     * with the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {*} value The value to wrap.
+     * @param {Function} [wrapper=identity] The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('fred, barney, & pebbles');
+     * // => '<p>fred, barney, &amp; pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      wrapper = wrapper == null ? identity : wrapper;
+      return partial(wrapper, value);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Casts `value` as an array if it's not one.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Lang
+     * @param {*} value The value to inspect.
+     * @returns {Array} Returns the cast array.
+     * @example
+     *
+     * _.castArray(1);
+     * // => [1]
+     *
+     * _.castArray({ 'a': 1 });
+     * // => [{ 'a': 1 }]
+     *
+     * _.castArray('abc');
+     * // => ['abc']
+     *
+     * _.castArray(null);
+     * // => [null]
+     *
+     * _.castArray(undefined);
+     * // => [undefined]
+     *
+     * _.castArray();
+     * // => []
+     *
+     * var array = [1, 2, 3];
+     * console.log(_.castArray(array) === array);
+     * // => true
+     */
+    function castArray() {
+      if (!arguments.length) {
+        return [];
+      }
+      var value = arguments[0];
+      return isArray(value) ? value : [value];
+    }
+
+    /**
+     * Creates a shallow clone of `value`.
+     *
+     * **Note:** This method is loosely based on the
+     * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+     * and supports cloning arrays, array buffers, booleans, date objects, maps,
+     * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+     * arrays. The own enumerable properties of `arguments` objects are cloned
+     * as plain objects. An empty object is returned for uncloneable values such
+     * as error objects, functions, DOM nodes, and WeakMaps.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @returns {*} Returns the cloned value.
+     * @see _.cloneDeep
+     * @example
+     *
+     * var objects = [{ 'a': 1 }, { 'b': 2 }];
+     *
+     * var shallow = _.clone(objects);
+     * console.log(shallow[0] === objects[0]);
+     * // => true
+     */
+    function clone(value) {
+      return baseClone(value, false, true);
+    }
+
+    /**
+     * This method is like `_.clone` except that it accepts `customizer` which
+     * is invoked to produce the cloned value. If `customizer` returns `undefined`,
+     * cloning is handled by the method instead. The `customizer` is invoked with
+     * up to four arguments; (value [, index|key, object, stack]).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @returns {*} Returns the cloned value.
+     * @see _.cloneDeepWith
+     * @example
+     *
+     * function customizer(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(false);
+     *   }
+     * }
+     *
+     * var el = _.cloneWith(document.body, customizer);
+     *
+     * console.log(el === document.body);
+     * // => false
+     * console.log(el.nodeName);
+     * // => 'BODY'
+     * console.log(el.childNodes.length);
+     * // => 0
+     */
+    function cloneWith(value, customizer) {
+      return baseClone(value, false, true, customizer);
+    }
+
+    /**
+     * This method is like `_.clone` except that it recursively clones `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Lang
+     * @param {*} value The value to recursively clone.
+     * @returns {*} Returns the deep cloned value.
+     * @see _.clone
+     * @example
+     *
+     * var objects = [{ 'a': 1 }, { 'b': 2 }];
+     *
+     * var deep = _.cloneDeep(objects);
+     * console.log(deep[0] === objects[0]);
+     * // => false
+     */
+    function cloneDeep(value) {
+      return baseClone(value, true, true);
+    }
+
+    /**
+     * This method is like `_.cloneWith` except that it recursively clones `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to recursively clone.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @returns {*} Returns the deep cloned value.
+     * @see _.cloneWith
+     * @example
+     *
+     * function customizer(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(true);
+     *   }
+     * }
+     *
+     * var el = _.cloneDeepWith(document.body, customizer);
+     *
+     * console.log(el === document.body);
+     * // => false
+     * console.log(el.nodeName);
+     * // => 'BODY'
+     * console.log(el.childNodes.length);
+     * // => 20
+     */
+    function cloneDeepWith(value, customizer) {
+      return baseClone(value, true, true, customizer);
+    }
+
+    /**
+     * Performs a
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * comparison between two values to determine if they are equivalent.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var other = { 'user': 'fred' };
+     *
+     * _.eq(object, object);
+     * // => true
+     *
+     * _.eq(object, other);
+     * // => false
+     *
+     * _.eq('a', 'a');
+     * // => true
+     *
+     * _.eq('a', Object('a'));
+     * // => false
+     *
+     * _.eq(NaN, NaN);
+     * // => true
+     */
+    function eq(value, other) {
+      return value === other || (value !== value && other !== other);
+    }
+
+    /**
+     * Checks if `value` is greater than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     * @see _.lt
+     * @example
+     *
+     * _.gt(3, 1);
+     * // => true
+     *
+     * _.gt(3, 3);
+     * // => false
+     *
+     * _.gt(1, 3);
+     * // => false
+     */
+    var gt = createRelationalOperation(baseGt);
+
+    /**
+     * Checks if `value` is greater than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than or equal to
+     *  `other`, else `false`.
+     * @see _.lte
+     * @example
+     *
+     * _.gte(3, 1);
+     * // => true
+     *
+     * _.gte(3, 3);
+     * // => true
+     *
+     * _.gte(1, 3);
+     * // => false
+     */
+    var gte = createRelationalOperation(function(value, other) {
+      return value >= other;
+    });
+
+    /**
+     * Checks if `value` is likely an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isArguments(function() { return arguments; }());
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    function isArguments(value) {
+      // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+      return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+        (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+    }
+
+    /**
+     * Checks if `value` is classified as an `Array` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @type {Function}
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     *
+     * _.isArray(document.body.children);
+     * // => false
+     *
+     * _.isArray('abc');
+     * // => false
+     *
+     * _.isArray(_.noop);
+     * // => false
+     */
+    var isArray = Array.isArray;
+
+    /**
+     * Checks if `value` is classified as an `ArrayBuffer` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isArrayBuffer(new ArrayBuffer(2));
+     * // => true
+     *
+     * _.isArrayBuffer(new Array(2));
+     * // => false
+     */
+    function isArrayBuffer(value) {
+      return isObjectLike(value) && objectToString.call(value) == arrayBufferTag;
+    }
+
+    /**
+     * Checks if `value` is array-like. A value is considered array-like if it's
+     * not a function and has a `value.length` that's an integer greater than or
+     * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+     * @example
+     *
+     * _.isArrayLike([1, 2, 3]);
+     * // => true
+     *
+     * _.isArrayLike(document.body.children);
+     * // => true
+     *
+     * _.isArrayLike('abc');
+     * // => true
+     *
+     * _.isArrayLike(_.noop);
+     * // => false
+     */
+    function isArrayLike(value) {
+      return value != null && isLength(getLength(value)) && !isFunction(value);
+    }
+
+    /**
+     * This method is like `_.isArrayLike` except that it also checks if `value`
+     * is an object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array-like object,
+     *  else `false`.
+     * @example
+     *
+     * _.isArrayLikeObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isArrayLikeObject(document.body.children);
+     * // => true
+     *
+     * _.isArrayLikeObject('abc');
+     * // => false
+     *
+     * _.isArrayLikeObject(_.noop);
+     * // => false
+     */
+    function isArrayLikeObject(value) {
+      return isObjectLike(value) && isArrayLike(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a boolean primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isBoolean(false);
+     * // => true
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false ||
+        (isObjectLike(value) && objectToString.call(value) == boolTag);
+    }
+
+    /**
+     * Checks if `value` is a buffer.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+     * @example
+     *
+     * _.isBuffer(new Buffer(2));
+     * // => true
+     *
+     * _.isBuffer(new Uint8Array(2));
+     * // => false
+     */
+    var isBuffer = !Buffer ? constant(false) : function(value) {
+      return value instanceof Buffer;
+    };
+
+    /**
+     * Checks if `value` is classified as a `Date` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     *
+     * _.isDate('Mon April 23 2012');
+     * // => false
+     */
+    function isDate(value) {
+      return isObjectLike(value) && objectToString.call(value) == dateTag;
+    }
+
+    /**
+     * Checks if `value` is likely a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a DOM element,
+     *  else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     *
+     * _.isElement('<body>');
+     * // => false
+     */
+    function isElement(value) {
+      return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
+    }
+
+    /**
+     * Checks if `value` is an empty object, collection, map, or set.
+     *
+     * Objects are considered empty if they have no own enumerable string keyed
+     * properties.
+     *
+     * Array-like values such as `arguments` objects, arrays, buffers, strings, or
+     * jQuery-like collections are considered empty if they have a `length` of `0`.
+     * Similarly, maps and sets are considered empty if they have a `size` of `0`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty(null);
+     * // => true
+     *
+     * _.isEmpty(true);
+     * // => true
+     *
+     * _.isEmpty(1);
+     * // => true
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({ 'a': 1 });
+     * // => false
+     */
+    function isEmpty(value) {
+      if (isArrayLike(value) &&
+          (isArray(value) || isString(value) || isFunction(value.splice) ||
+            isArguments(value) || isBuffer(value))) {
+        return !value.length;
+      }
+      if (isObjectLike(value)) {
+        var tag = getTag(value);
+        if (tag == mapTag || tag == setTag) {
+          return !value.size;
+        }
+      }
+      for (var key in value) {
+        if (hasOwnProperty.call(value, key)) {
+          return false;
+        }
+      }
+      return !(nonEnumShadows && keys(value).length);
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent.
+     *
+     * **Note:** This method supports comparing arrays, array buffers, booleans,
+     * date objects, error objects, maps, numbers, `Object` objects, regexes,
+     * sets, strings, symbols, and typed arrays. `Object` objects are compared
+     * by their own, not inherited, enumerable properties. Functions and DOM
+     * nodes are **not** supported.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if the values are equivalent,
+     *  else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var other = { 'user': 'fred' };
+     *
+     * _.isEqual(object, other);
+     * // => true
+     *
+     * object === other;
+     * // => false
+     */
+    function isEqual(value, other) {
+      return baseIsEqual(value, other);
+    }
+
+    /**
+     * This method is like `_.isEqual` except that it accepts `customizer` which
+     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+     * are handled by the method instead. The `customizer` is invoked with up to
+     * six arguments: (objValue, othValue [, index|key, object, other, stack]).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if the values are equivalent,
+     *  else `false`.
+     * @example
+     *
+     * function isGreeting(value) {
+     *   return /^h(?:i|ello)$/.test(value);
+     * }
+     *
+     * function customizer(objValue, othValue) {
+     *   if (isGreeting(objValue) && isGreeting(othValue)) {
+     *     return true;
+     *   }
+     * }
+     *
+     * var array = ['hello', 'goodbye'];
+     * var other = ['hi', 'goodbye'];
+     *
+     * _.isEqualWith(array, other, customizer);
+     * // => true
+     */
+    function isEqualWith(value, other, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      var result = customizer ? customizer(value, other) : undefined;
+      return result === undefined ? baseIsEqual(value, other, customizer) : !!result;
+    }
+
+    /**
+     * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+     * `SyntaxError`, `TypeError`, or `URIError` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an error object,
+     *  else `false`.
+     * @example
+     *
+     * _.isError(new Error);
+     * // => true
+     *
+     * _.isError(Error);
+     * // => false
+     */
+    function isError(value) {
+      if (!isObjectLike(value)) {
+        return false;
+      }
+      return (objectToString.call(value) == errorTag) ||
+        (typeof value.message == 'string' && typeof value.name == 'string');
+    }
+
+    /**
+     * Checks if `value` is a finite primitive number.
+     *
+     * **Note:** This method is based on
+     * [`Number.isFinite`](https://mdn.io/Number/isFinite).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a finite number,
+     *  else `false`.
+     * @example
+     *
+     * _.isFinite(3);
+     * // => true
+     *
+     * _.isFinite(Number.MAX_VALUE);
+     * // => true
+     *
+     * _.isFinite(3.14);
+     * // => true
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     */
+    function isFinite(value) {
+      return typeof value == 'number' && nativeIsFinite(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Function` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     *
+     * _.isFunction(/abc/);
+     * // => false
+     */
+    function isFunction(value) {
+      // The use of `Object#toString` avoids issues with the `typeof` operator
+      // in Safari 8 which returns 'object' for typed array and weak map constructors,
+      // and PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+      var tag = isObject(value) ? objectToString.call(value) : '';
+      return tag == funcTag || tag == genTag;
+    }
+
+    /**
+     * Checks if `value` is an integer.
+     *
+     * **Note:** This method is based on
+     * [`Number.isInteger`](https://mdn.io/Number/isInteger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
+     * @example
+     *
+     * _.isInteger(3);
+     * // => true
+     *
+     * _.isInteger(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isInteger(Infinity);
+     * // => false
+     *
+     * _.isInteger('3');
+     * // => false
+     */
+    function isInteger(value) {
+      return typeof value == 'number' && value == toInteger(value);
+    }
+
+    /**
+     * Checks if `value` is a valid array-like length.
+     *
+     * **Note:** This function is loosely based on
+     * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a valid length,
+     *  else `false`.
+     * @example
+     *
+     * _.isLength(3);
+     * // => true
+     *
+     * _.isLength(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isLength(Infinity);
+     * // => false
+     *
+     * _.isLength('3');
+     * // => false
+     */
+    function isLength(value) {
+      return typeof value == 'number' &&
+        value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is the
+     * [language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types)
+     * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(_.noop);
+     * // => true
+     *
+     * _.isObject(null);
+     * // => false
+     */
+    function isObject(value) {
+      var type = typeof value;
+      return !!value && (type == 'object' || type == 'function');
+    }
+
+    /**
+     * Checks if `value` is object-like. A value is object-like if it's not `null`
+     * and has a `typeof` result of "object".
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+     * @example
+     *
+     * _.isObjectLike({});
+     * // => true
+     *
+     * _.isObjectLike([1, 2, 3]);
+     * // => true
+     *
+     * _.isObjectLike(_.noop);
+     * // => false
+     *
+     * _.isObjectLike(null);
+     * // => false
+     */
+    function isObjectLike(value) {
+      return !!value && typeof value == 'object';
+    }
+
+    /**
+     * Checks if `value` is classified as a `Map` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isMap(new Map);
+     * // => true
+     *
+     * _.isMap(new WeakMap);
+     * // => false
+     */
+    function isMap(value) {
+      return isObjectLike(value) && getTag(value) == mapTag;
+    }
+
+    /**
+     * Performs a partial deep comparison between `object` and `source` to
+     * determine if `object` contains equivalent property values. This method is
+     * equivalent to a `_.matches` function when `source` is partially applied.
+     *
+     * **Note:** This method supports comparing the same values as `_.isEqual`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred', 'age': 40 };
+     *
+     * _.isMatch(object, { 'age': 40 });
+     * // => true
+     *
+     * _.isMatch(object, { 'age': 36 });
+     * // => false
+     */
+    function isMatch(object, source) {
+      return object === source || baseIsMatch(object, source, getMatchData(source));
+    }
+
+    /**
+     * This method is like `_.isMatch` except that it accepts `customizer` which
+     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+     * are handled by the method instead. The `customizer` is invoked with five
+     * arguments: (objValue, srcValue, index|key, object, source).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * function isGreeting(value) {
+     *   return /^h(?:i|ello)$/.test(value);
+     * }
+     *
+     * function customizer(objValue, srcValue) {
+     *   if (isGreeting(objValue) && isGreeting(srcValue)) {
+     *     return true;
+     *   }
+     * }
+     *
+     * var object = { 'greeting': 'hello' };
+     * var source = { 'greeting': 'hi' };
+     *
+     * _.isMatchWith(object, source, customizer);
+     * // => true
+     */
+    function isMatchWith(object, source, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return baseIsMatch(object, source, getMatchData(source), customizer);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * **Note:** This method is based on
+     * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+     * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+     * `undefined` and other non-number values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // An `NaN` primitive is the only value that is not equal to itself.
+      // Perform the `toStringTag` check first to avoid errors with some
+      // ActiveX objects in IE.
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is a native function.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a native function,
+     *  else `false`.
+     * @example
+     *
+     * _.isNative(Array.prototype.push);
+     * // => true
+     *
+     * _.isNative(_);
+     * // => false
+     */
+    function isNative(value) {
+      if (!isObject(value)) {
+        return false;
+      }
+      var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
+      return pattern.test(toSource(value));
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(void 0);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is `null` or `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
+     * @example
+     *
+     * _.isNil(null);
+     * // => true
+     *
+     * _.isNil(void 0);
+     * // => true
+     *
+     * _.isNil(NaN);
+     * // => false
+     */
+    function isNil(value) {
+      return value == null;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Number` primitive or object.
+     *
+     * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+     * classified as numbers, use the `_.isFinite` method.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isNumber(3);
+     * // => true
+     *
+     * _.isNumber(Number.MIN_VALUE);
+     * // => true
+     *
+     * _.isNumber(Infinity);
+     * // => true
+     *
+     * _.isNumber('3');
+     * // => false
+     */
+    function isNumber(value) {
+      return typeof value == 'number' ||
+        (isObjectLike(value) && objectToString.call(value) == numberTag);
+    }
+
+    /**
+     * Checks if `value` is a plain object, that is, an object created by the
+     * `Object` constructor or one with a `[[Prototype]]` of `null`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.8.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object,
+     *  else `false`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     * }
+     *
+     * _.isPlainObject(new Foo);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     *
+     * _.isPlainObject(Object.create(null));
+     * // => true
+     */
+    function isPlainObject(value) {
+      if (!isObjectLike(value) ||
+          objectToString.call(value) != objectTag || isHostObject(value)) {
+        return false;
+      }
+      var proto = getPrototype(value);
+      if (proto === null) {
+        return true;
+      }
+      var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+      return (typeof Ctor == 'function' &&
+        Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+    }
+
+    /**
+     * Checks if `value` is classified as a `RegExp` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isRegExp(/abc/);
+     * // => true
+     *
+     * _.isRegExp('/abc/');
+     * // => false
+     */
+    function isRegExp(value) {
+      return isObject(value) && objectToString.call(value) == regexpTag;
+    }
+
+    /**
+     * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+     * double precision number which isn't the result of a rounded unsafe integer.
+     *
+     * **Note:** This method is based on
+     * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a safe integer,
+     *  else `false`.
+     * @example
+     *
+     * _.isSafeInteger(3);
+     * // => true
+     *
+     * _.isSafeInteger(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isSafeInteger(Infinity);
+     * // => false
+     *
+     * _.isSafeInteger('3');
+     * // => false
+     */
+    function isSafeInteger(value) {
+      return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Set` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isSet(new Set);
+     * // => true
+     *
+     * _.isSet(new WeakSet);
+     * // => false
+     */
+    function isSet(value) {
+      return isObjectLike(value) && getTag(value) == setTag;
+    }
+
+    /**
+     * Checks if `value` is classified as a `String` primitive or object.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isString('abc');
+     * // => true
+     *
+     * _.isString(1);
+     * // => false
+     */
+    function isString(value) {
+      return typeof value == 'string' ||
+        (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Symbol` primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isSymbol(Symbol.iterator);
+     * // => true
+     *
+     * _.isSymbol('abc');
+     * // => false
+     */
+    function isSymbol(value) {
+      return typeof value == 'symbol' ||
+        (isObjectLike(value) && objectToString.call(value) == symbolTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a typed array.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isTypedArray(new Uint8Array);
+     * // => true
+     *
+     * _.isTypedArray([]);
+     * // => false
+     */
+    function isTypedArray(value) {
+      return isObjectLike(value) &&
+        isLength(value.length) && !!typedArrayTags[objectToString.call(value)];
+    }
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     *
+     * _.isUndefined(null);
+     * // => false
+     */
+    function isUndefined(value) {
+      return value === undefined;
+    }
+
+    /**
+     * Checks if `value` is classified as a `WeakMap` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isWeakMap(new WeakMap);
+     * // => true
+     *
+     * _.isWeakMap(new Map);
+     * // => false
+     */
+    function isWeakMap(value) {
+      return isObjectLike(value) && getTag(value) == weakMapTag;
+    }
+
+    /**
+     * Checks if `value` is classified as a `WeakSet` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isWeakSet(new WeakSet);
+     * // => true
+     *
+     * _.isWeakSet(new Set);
+     * // => false
+     */
+    function isWeakSet(value) {
+      return isObjectLike(value) && objectToString.call(value) == weakSetTag;
+    }
+
+    /**
+     * Checks if `value` is less than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     * @see _.gt
+     * @example
+     *
+     * _.lt(1, 3);
+     * // => true
+     *
+     * _.lt(3, 3);
+     * // => false
+     *
+     * _.lt(3, 1);
+     * // => false
+     */
+    var lt = createRelationalOperation(baseLt);
+
+    /**
+     * Checks if `value` is less than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than or equal to
+     *  `other`, else `false`.
+     * @see _.gte
+     * @example
+     *
+     * _.lte(1, 3);
+     * // => true
+     *
+     * _.lte(3, 3);
+     * // => true
+     *
+     * _.lte(3, 1);
+     * // => false
+     */
+    var lte = createRelationalOperation(function(value, other) {
+      return value <= other;
+    });
+
+    /**
+     * Converts `value` to an array.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the converted array.
+     * @example
+     *
+     * _.toArray({ 'a': 1, 'b': 2 });
+     * // => [1, 2]
+     *
+     * _.toArray('abc');
+     * // => ['a', 'b', 'c']
+     *
+     * _.toArray(1);
+     * // => []
+     *
+     * _.toArray(null);
+     * // => []
+     */
+    function toArray(value) {
+      if (!value) {
+        return [];
+      }
+      if (isArrayLike(value)) {
+        return isString(value) ? stringToArray(value) : copyArray(value);
+      }
+      if (iteratorSymbol && value[iteratorSymbol]) {
+        return iteratorToArray(value[iteratorSymbol]());
+      }
+      var tag = getTag(value),
+          func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);
+
+      return func(value);
+    }
+
+    /**
+     * Converts `value` to an integer.
+     *
+     * **Note:** This function is loosely based on
+     * [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toInteger(3);
+     * // => 3
+     *
+     * _.toInteger(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toInteger(Infinity);
+     * // => 1.7976931348623157e+308
+     *
+     * _.toInteger('3');
+     * // => 3
+     */
+    function toInteger(value) {
+      if (!value) {
+        return value === 0 ? value : 0;
+      }
+      value = toNumber(value);
+      if (value === INFINITY || value === -INFINITY) {
+        var sign = (value < 0 ? -1 : 1);
+        return sign * MAX_INTEGER;
+      }
+      var remainder = value % 1;
+      return value === value ? (remainder ? value - remainder : value) : 0;
+    }
+
+    /**
+     * Converts `value` to an integer suitable for use as the length of an
+     * array-like object.
+     *
+     * **Note:** This method is based on
+     * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toLength(3);
+     * // => 3
+     *
+     * _.toLength(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toLength(Infinity);
+     * // => 4294967295
+     *
+     * _.toLength('3');
+     * // => 3
+     */
+    function toLength(value) {
+      return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
+    }
+
+    /**
+     * Converts `value` to a number.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     * @example
+     *
+     * _.toNumber(3);
+     * // => 3
+     *
+     * _.toNumber(Number.MIN_VALUE);
+     * // => 5e-324
+     *
+     * _.toNumber(Infinity);
+     * // => Infinity
+     *
+     * _.toNumber('3');
+     * // => 3
+     */
+    function toNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      if (isObject(value)) {
+        var other = isFunction(value.valueOf) ? value.valueOf() : value;
+        value = isObject(other) ? (other + '') : other;
+      }
+      if (typeof value != 'string') {
+        return value === 0 ? value : +value;
+      }
+      value = value.replace(reTrim, '');
+      var isBinary = reIsBinary.test(value);
+      return (isBinary || reIsOctal.test(value))
+        ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
+        : (reIsBadHex.test(value) ? NAN : +value);
+    }
+
+    /**
+     * Converts `value` to a plain object flattening inherited enumerable string
+     * keyed properties of `value` to own properties of the plain object.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Object} Returns the converted plain object.
+     * @example
+     *
+     * function Foo() {
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.assign({ 'a': 1 }, new Foo);
+     * // => { 'a': 1, 'b': 2 }
+     *
+     * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+     * // => { 'a': 1, 'b': 2, 'c': 3 }
+     */
+    function toPlainObject(value) {
+      return copyObject(value, keysIn(value));
+    }
+
+    /**
+     * Converts `value` to a safe integer. A safe integer can be compared and
+     * represented correctly.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toSafeInteger(3);
+     * // => 3
+     *
+     * _.toSafeInteger(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toSafeInteger(Infinity);
+     * // => 9007199254740991
+     *
+     * _.toSafeInteger('3');
+     * // => 3
+     */
+    function toSafeInteger(value) {
+      return baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
+    }
+
+    /**
+     * Converts `value` to a string. An empty string is returned for `null`
+     * and `undefined` values. The sign of `-0` is preserved.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to process.
+     * @returns {string} Returns the string.
+     * @example
+     *
+     * _.toString(null);
+     * // => ''
+     *
+     * _.toString(-0);
+     * // => '-0'
+     *
+     * _.toString([1, 2, 3]);
+     * // => '1,2,3'
+     */
+    function toString(value) {
+      return value == null ? '' : baseToString(value);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Assigns own enumerable string keyed properties of source objects to the
+     * destination object. Source objects are applied from left to right.
+     * Subsequent sources overwrite property assignments of previous sources.
+     *
+     * **Note:** This method mutates `object` and is loosely based on
+     * [`Object.assign`](https://mdn.io/Object/assign).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.assignIn
+     * @example
+     *
+     * function Foo() {
+     *   this.c = 3;
+     * }
+     *
+     * function Bar() {
+     *   this.e = 5;
+     * }
+     *
+     * Foo.prototype.d = 4;
+     * Bar.prototype.f = 6;
+     *
+     * _.assign({ 'a': 1 }, new Foo, new Bar);
+     * // => { 'a': 1, 'c': 3, 'e': 5 }
+     */
+    var assign = createAssigner(function(object, source) {
+      if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) {
+        copyObject(source, keys(source), object);
+        return;
+      }
+      for (var key in source) {
+        if (hasOwnProperty.call(source, key)) {
+          assignValue(object, key, source[key]);
+        }
+      }
+    });
+
+    /**
+     * This method is like `_.assign` except that it iterates over own and
+     * inherited source properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias extend
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.assign
+     * @example
+     *
+     * function Foo() {
+     *   this.b = 2;
+     * }
+     *
+     * function Bar() {
+     *   this.d = 4;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     * Bar.prototype.e = 5;
+     *
+     * _.assignIn({ 'a': 1 }, new Foo, new Bar);
+     * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }
+     */
+    var assignIn = createAssigner(function(object, source) {
+      if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) {
+        copyObject(source, keysIn(source), object);
+        return;
+      }
+      for (var key in source) {
+        assignValue(object, key, source[key]);
+      }
+    });
+
+    /**
+     * This method is like `_.assignIn` except that it accepts `customizer`
+     * which is invoked to produce the assigned values. If `customizer` returns
+     * `undefined`, assignment is handled by the method instead. The `customizer`
+     * is invoked with five arguments: (objValue, srcValue, key, object, source).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias extendWith
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @see _.assignWith
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   return _.isUndefined(objValue) ? srcValue : objValue;
+     * }
+     *
+     * var defaults = _.partialRight(_.assignInWith, customizer);
+     *
+     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
+      copyObject(source, keysIn(source), object, customizer);
+    });
+
+    /**
+     * This method is like `_.assign` except that it accepts `customizer`
+     * which is invoked to produce the assigned values. If `customizer` returns
+     * `undefined`, assignment is handled by the method instead. The `customizer`
+     * is invoked with five arguments: (objValue, srcValue, key, object, source).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @see _.assignInWith
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   return _.isUndefined(objValue) ? srcValue : objValue;
+     * }
+     *
+     * var defaults = _.partialRight(_.assignWith, customizer);
+     *
+     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
+      copyObject(source, keys(source), object, customizer);
+    });
+
+    /**
+     * Creates an array of values corresponding to `paths` of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {...(string|string[])} [paths] The property paths of elements to pick.
+     * @returns {Array} Returns the new array of picked elements.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _.at(object, ['a[0].b.c', 'a[1]']);
+     * // => [3, 4]
+     *
+     * _.at(['a', 'b', 'c'], 0, 2);
+     * // => ['a', 'c']
+     */
+    var at = rest(function(object, paths) {
+      return baseAt(object, baseFlatten(paths, 1));
+    });
+
+    /**
+     * Creates an object that inherits from the `prototype` object. If a
+     * `properties` object is given, its own enumerable string keyed properties
+     * are assigned to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.3.0
+     * @category Object
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, {
+     *   'constructor': Circle
+     * });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties) {
+      var result = baseCreate(prototype);
+      return properties ? baseAssign(result, properties) : result;
+    }
+
+    /**
+     * Assigns own and inherited enumerable string keyed properties of source
+     * objects to the destination object for all destination properties that
+     * resolve to `undefined`. Source objects are applied from left to right.
+     * Once a property is set, additional values of the same property are ignored.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.defaultsDeep
+     * @example
+     *
+     * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+     * // => { 'user': 'barney', 'age': 36 }
+     */
+    var defaults = rest(function(args) {
+      args.push(undefined, assignInDefaults);
+      return apply(assignInWith, undefined, args);
+    });
+
+    /**
+     * This method is like `_.defaults` except that it recursively assigns
+     * default properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.defaults
+     * @example
+     *
+     * _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
+     * // => { 'user': { 'name': 'barney', 'age': 36 } }
+     *
+     */
+    var defaultsDeep = rest(function(args) {
+      args.push(undefined, mergeDefaults);
+      return apply(mergeWith, undefined, args);
+    });
+
+    /**
+     * This method is like `_.find` except that it returns the key of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Object
+     * @param {Object} object The object to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {string|undefined} Returns the key of the matched element,
+     *  else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findKey(users, function(o) { return o.age < 40; });
+     * // => 'barney' (iteration order is not guaranteed)
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findKey(users, { 'age': 1, 'active': true });
+     * // => 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findKey(users, ['active', false]);
+     * // => 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findKey(users, 'active');
+     * // => 'barney'
+     */
+    function findKey(object, predicate) {
+      return baseFind(object, getIteratee(predicate, 3), baseForOwn, true);
+    }
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements of
+     * a collection in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {string|undefined} Returns the key of the matched element,
+     *  else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findLastKey(users, function(o) { return o.age < 40; });
+     * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastKey(users, { 'age': 36, 'active': true });
+     * // => 'barney'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastKey(users, ['active', false]);
+     * // => 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastKey(users, 'active');
+     * // => 'pebbles'
+     */
+    function findLastKey(object, predicate) {
+      return baseFind(object, getIteratee(predicate, 3), baseForOwnRight, true);
+    }
+
+    /**
+     * Iterates over own and inherited enumerable string keyed properties of an
+     * object and invokes `iteratee` for each property. The iteratee is invoked
+     * with three arguments: (value, key, object). Iteratee functions may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forInRight
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forIn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
+     */
+    function forIn(object, iteratee) {
+      return object == null
+        ? object
+        : baseFor(object, getIteratee(iteratee), keysIn);
+    }
+
+    /**
+     * This method is like `_.forIn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forInRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
+     */
+    function forInRight(object, iteratee) {
+      return object == null
+        ? object
+        : baseForRight(object, getIteratee(iteratee), keysIn);
+    }
+
+    /**
+     * Iterates over own enumerable string keyed properties of an object and
+     * invokes `iteratee` for each property. The iteratee is invoked with three
+     * arguments: (value, key, object). Iteratee functions may exit iteration
+     * early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forOwnRight
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forOwn(object, iteratee) {
+      return object && baseForOwn(object, getIteratee(iteratee));
+    }
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forOwn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwnRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
+     */
+    function forOwnRight(object, iteratee) {
+      return object && baseForOwnRight(object, getIteratee(iteratee));
+    }
+
+    /**
+     * Creates an array of function property names from own enumerable properties
+     * of `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the new array of property names.
+     * @see _.functionsIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = _.constant('a');
+     *   this.b = _.constant('b');
+     * }
+     *
+     * Foo.prototype.c = _.constant('c');
+     *
+     * _.functions(new Foo);
+     * // => ['a', 'b']
+     */
+    function functions(object) {
+      return object == null ? [] : baseFunctions(object, keys(object));
+    }
+
+    /**
+     * Creates an array of function property names from own and inherited
+     * enumerable properties of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the new array of property names.
+     * @see _.functions
+     * @example
+     *
+     * function Foo() {
+     *   this.a = _.constant('a');
+     *   this.b = _.constant('b');
+     * }
+     *
+     * Foo.prototype.c = _.constant('c');
+     *
+     * _.functionsIn(new Foo);
+     * // => ['a', 'b', 'c']
+     */
+    function functionsIn(object) {
+      return object == null ? [] : baseFunctions(object, keysIn(object));
+    }
+
+    /**
+     * Gets the value at `path` of `object`. If the resolved value is
+     * `undefined`, the `defaultValue` is used in its place.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.get(object, 'a[0].b.c');
+     * // => 3
+     *
+     * _.get(object, ['a', '0', 'b', 'c']);
+     * // => 3
+     *
+     * _.get(object, 'a.b.c', 'default');
+     * // => 'default'
+     */
+    function get(object, path, defaultValue) {
+      var result = object == null ? undefined : baseGet(object, path);
+      return result === undefined ? defaultValue : result;
+    }
+
+    /**
+     * Checks if `path` is a direct property of `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     * @example
+     *
+     * var object = { 'a': { 'b': 2 } };
+     * var other = _.create({ 'a': _.create({ 'b': 2 }) });
+     *
+     * _.has(object, 'a');
+     * // => true
+     *
+     * _.has(object, 'a.b');
+     * // => true
+     *
+     * _.has(object, ['a', 'b']);
+     * // => true
+     *
+     * _.has(other, 'a');
+     * // => false
+     */
+    function has(object, path) {
+      return object != null && hasPath(object, path, baseHas);
+    }
+
+    /**
+     * Checks if `path` is a direct or inherited property of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     * @example
+     *
+     * var object = _.create({ 'a': _.create({ 'b': 2 }) });
+     *
+     * _.hasIn(object, 'a');
+     * // => true
+     *
+     * _.hasIn(object, 'a.b');
+     * // => true
+     *
+     * _.hasIn(object, ['a', 'b']);
+     * // => true
+     *
+     * _.hasIn(object, 'b');
+     * // => false
+     */
+    function hasIn(object, path) {
+      return object != null && hasPath(object, path, baseHasIn);
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of `object`.
+     * If `object` contains duplicate values, subsequent values overwrite
+     * property assignments of previous values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.7.0
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invert(object);
+     * // => { '1': 'c', '2': 'b' }
+     */
+    var invert = createInverter(function(result, value, key) {
+      result[value] = key;
+    }, constant(identity));
+
+    /**
+     * This method is like `_.invert` except that the inverted object is generated
+     * from the results of running each element of `object` thru `iteratee`. The
+     * corresponding inverted value of each inverted key is an array of keys
+     * responsible for generating the inverted value. The iteratee is invoked
+     * with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invertBy(object);
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     *
+     * _.invertBy(object, function(value) {
+     *   return 'group' + value;
+     * });
+     * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
+     */
+    var invertBy = createInverter(function(result, value, key) {
+      if (hasOwnProperty.call(result, value)) {
+        result[value].push(key);
+      } else {
+        result[value] = [key];
+      }
+    }, getIteratee);
+
+    /**
+     * Invokes the method at `path` of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+     *
+     * _.invoke(object, 'a[0].b.c.slice', 1, 3);
+     * // => [2, 3]
+     */
+    var invoke = rest(baseInvoke);
+
+    /**
+     * Creates an array of the own enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects. See the
+     * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+     * for more details.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keys(new Foo);
+     * // => ['a', 'b'] (iteration order is not guaranteed)
+     *
+     * _.keys('hi');
+     * // => ['0', '1']
+     */
+    function keys(object) {
+      var isProto = isPrototype(object);
+      if (!(isProto || isArrayLike(object))) {
+        return baseKeys(object);
+      }
+      var indexes = indexKeys(object),
+          skipIndexes = !!indexes,
+          result = indexes || [],
+          length = result.length;
+
+      for (var key in object) {
+        if (baseHas(object, key) &&
+            !(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+            !(isProto && key == 'constructor')) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keysIn(new Foo);
+     * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+     */
+    function keysIn(object) {
+      var index = -1,
+          isProto = isPrototype(object),
+          props = baseKeysIn(object),
+          propsLength = props.length,
+          indexes = indexKeys(object),
+          skipIndexes = !!indexes,
+          result = indexes || [],
+          length = result.length;
+
+      while (++index < propsLength) {
+        var key = props[index];
+        if (!(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+            !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The opposite of `_.mapValues`; this method creates an object with the
+     * same values as `object` and keys generated by running each own enumerable
+     * string keyed property of `object` thru `iteratee`. The iteratee is invoked
+     * with three arguments: (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Object} Returns the new mapped object.
+     * @see _.mapValues
+     * @example
+     *
+     * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   return key + value;
+     * });
+     * // => { 'a1': 1, 'b2': 2 }
+     */
+    function mapKeys(object, iteratee) {
+      var result = {};
+      iteratee = getIteratee(iteratee, 3);
+
+      baseForOwn(object, function(value, key, object) {
+        result[iteratee(value, key, object)] = value;
+      });
+      return result;
+    }
+
+    /**
+     * Creates an object with the same keys as `object` and values generated
+     * by running each own enumerable string keyed property of `object` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Object} Returns the new mapped object.
+     * @see _.mapKeys
+     * @example
+     *
+     * var users = {
+     *   'fred':    { 'user': 'fred',    'age': 40 },
+     *   'pebbles': { 'user': 'pebbles', 'age': 1 }
+     * };
+     *
+     * _.mapValues(users, function(o) { return o.age; });
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.mapValues(users, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     */
+    function mapValues(object, iteratee) {
+      var result = {};
+      iteratee = getIteratee(iteratee, 3);
+
+      baseForOwn(object, function(value, key, object) {
+        result[key] = iteratee(value, key, object);
+      });
+      return result;
+    }
+
+    /**
+     * This method is like `_.assign` except that it recursively merges own and
+     * inherited enumerable string keyed properties of source objects into the
+     * destination object. Source properties that resolve to `undefined` are
+     * skipped if a destination value exists. Array and plain object properties
+     * are merged recursively.Other objects and value types are overridden by
+     * assignment. Source objects are applied from left to right. Subsequent
+     * sources overwrite property assignments of previous sources.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var users = {
+     *   'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
+     * };
+     *
+     * var ages = {
+     *   'data': [{ 'age': 36 }, { 'age': 40 }]
+     * };
+     *
+     * _.merge(users, ages);
+     * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
+     */
+    var merge = createAssigner(function(object, source, srcIndex) {
+      baseMerge(object, source, srcIndex);
+    });
+
+    /**
+     * This method is like `_.merge` except that it accepts `customizer` which
+     * is invoked to produce the merged values of the destination and source
+     * properties. If `customizer` returns `undefined`, merging is handled by the
+     * method instead. The `customizer` is invoked with seven arguments:
+     * (objValue, srcValue, key, object, source, stack).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} customizer The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   if (_.isArray(objValue)) {
+     *     return objValue.concat(srcValue);
+     *   }
+     * }
+     *
+     * var object = {
+     *   'fruits': ['apple'],
+     *   'vegetables': ['beet']
+     * };
+     *
+     * var other = {
+     *   'fruits': ['banana'],
+     *   'vegetables': ['carrot']
+     * };
+     *
+     * _.mergeWith(object, other, customizer);
+     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
+     */
+    var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
+      baseMerge(object, source, srcIndex, customizer);
+    });
+
+    /**
+     * The opposite of `_.pick`; this method creates an object composed of the
+     * own and inherited enumerable string keyed properties of `object` that are
+     * not omitted.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {...(string|string[])} [props] The property identifiers to omit.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.omit(object, ['a', 'c']);
+     * // => { 'b': '2' }
+     */
+    var omit = rest(function(object, props) {
+      if (object == null) {
+        return {};
+      }
+      props = arrayMap(baseFlatten(props, 1), toKey);
+      return basePick(object, baseDifference(getAllKeysIn(object), props));
+    });
+
+    /**
+     * The opposite of `_.pickBy`; this method creates an object composed of
+     * the own and inherited enumerable string keyed properties of `object` that
+     * `predicate` doesn't return truthy for. The predicate is invoked with two
+     * arguments: (value, key).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per property.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.omitBy(object, _.isNumber);
+     * // => { 'b': '2' }
+     */
+    function omitBy(object, predicate) {
+      predicate = getIteratee(predicate);
+      return basePickBy(object, function(value, key) {
+        return !predicate(value, key);
+      });
+    }
+
+    /**
+     * Creates an object composed of the picked `object` properties.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {...(string|string[])} [props] The property identifiers to pick.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.pick(object, ['a', 'c']);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    var pick = rest(function(object, props) {
+      return object == null ? {} : basePick(object, arrayMap(baseFlatten(props, 1), toKey));
+    });
+
+    /**
+     * Creates an object composed of the `object` properties `predicate` returns
+     * truthy for. The predicate is invoked with two arguments: (value, key).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per property.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.pickBy(object, _.isNumber);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    function pickBy(object, predicate) {
+      return object == null ? {} : basePickBy(object, getIteratee(predicate));
+    }
+
+    /**
+     * This method is like `_.get` except that if the resolved value is a
+     * function it's invoked with the `this` binding of its parent object and
+     * its result is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to resolve.
+     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+     *
+     * _.result(object, 'a[0].b.c1');
+     * // => 3
+     *
+     * _.result(object, 'a[0].b.c2');
+     * // => 4
+     *
+     * _.result(object, 'a[0].b.c3', 'default');
+     * // => 'default'
+     *
+     * _.result(object, 'a[0].b.c3', _.constant('default'));
+     * // => 'default'
+     */
+    function result(object, path, defaultValue) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var index = -1,
+          length = path.length;
+
+      // Ensure the loop is entered when path is empty.
+      if (!length) {
+        object = undefined;
+        length = 1;
+      }
+      while (++index < length) {
+        var value = object == null ? undefined : object[toKey(path[index])];
+        if (value === undefined) {
+          index = length;
+          value = defaultValue;
+        }
+        object = isFunction(value) ? value.call(object) : value;
+      }
+      return object;
+    }
+
+    /**
+     * Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
+     * it's created. Arrays are created for missing index properties while objects
+     * are created for all other missing properties. Use `_.setWith` to customize
+     * `path` creation.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.set(object, 'a[0].b.c', 4);
+     * console.log(object.a[0].b.c);
+     * // => 4
+     *
+     * _.set(object, ['x', '0', 'y', 'z'], 5);
+     * console.log(object.x[0].y.z);
+     * // => 5
+     */
+    function set(object, path, value) {
+      return object == null ? object : baseSet(object, path, value);
+    }
+
+    /**
+     * This method is like `_.set` except that it accepts `customizer` which is
+     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+     * path creation is handled by the method instead. The `customizer` is invoked
+     * with three arguments: (nsValue, key, nsObject).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {};
+     *
+     * _.setWith(object, '[0][1]', 'a', Object);
+     * // => { '0': { '1': 'a' } }
+     */
+    function setWith(object, path, value, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return object == null ? object : baseSet(object, path, value, customizer);
+    }
+
+    /**
+     * Creates an array of own enumerable string keyed-value pairs for `object`
+     * which can be consumed by `_.fromPairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias entries
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the new array of key-value pairs.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.toPairs(new Foo);
+     * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+     */
+    function toPairs(object) {
+      return baseToPairs(object, keys(object));
+    }
+
+    /**
+     * Creates an array of own and inherited enumerable string keyed-value pairs
+     * for `object` which can be consumed by `_.fromPairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias entriesIn
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the new array of key-value pairs.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.toPairsIn(new Foo);
+     * // => [['a', 1], ['b', 2], ['c', 1]] (iteration order is not guaranteed)
+     */
+    function toPairsIn(object) {
+      return baseToPairs(object, keysIn(object));
+    }
+
+    /**
+     * An alternative to `_.reduce`; this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own
+     * enumerable string keyed properties thru `iteratee`, with each invocation
+     * potentially mutating the `accumulator` object. The iteratee is invoked
+     * with four arguments: (accumulator, value, key, object). Iteratee functions
+     * may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Object
+     * @param {Array|Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * _.transform([2, 3, 4], function(result, n) {
+     *   result.push(n *= n);
+     *   return n % 2 == 0;
+     * }, []);
+     * // => [4, 9]
+     *
+     * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     */
+    function transform(object, iteratee, accumulator) {
+      var isArr = isArray(object) || isTypedArray(object);
+      iteratee = getIteratee(iteratee, 4);
+
+      if (accumulator == null) {
+        if (isArr || isObject(object)) {
+          var Ctor = object.constructor;
+          if (isArr) {
+            accumulator = isArray(object) ? new Ctor : [];
+          } else {
+            accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
+          }
+        } else {
+          accumulator = {};
+        }
+      }
+      (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) {
+        return iteratee(accumulator, value, index, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * Removes the property at `path` of `object`.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 7 } }] };
+     * _.unset(object, 'a[0].b.c');
+     * // => true
+     *
+     * console.log(object);
+     * // => { 'a': [{ 'b': {} }] };
+     *
+     * _.unset(object, ['a', '0', 'b', 'c']);
+     * // => true
+     *
+     * console.log(object);
+     * // => { 'a': [{ 'b': {} }] };
+     */
+    function unset(object, path) {
+      return object == null ? true : baseUnset(object, path);
+    }
+
+    /**
+     * This method is like `_.set` except that accepts `updater` to produce the
+     * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+     * is invoked with one argument: (value).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {Function} updater The function to produce the updated value.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.update(object, 'a[0].b.c', function(n) { return n * n; });
+     * console.log(object.a[0].b.c);
+     * // => 9
+     *
+     * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+     * console.log(object.x[0].y.z);
+     * // => 0
+     */
+    function update(object, path, updater) {
+      return object == null ? object : baseUpdate(object, path, castFunction(updater));
+    }
+
+    /**
+     * This method is like `_.update` except that it accepts `customizer` which is
+     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+     * path creation is handled by the method instead. The `customizer` is invoked
+     * with three arguments: (nsValue, key, nsObject).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {};
+     *
+     * _.updateWith(object, '[0][1]', _.constant('a'), Object);
+     * // => { '0': { '1': 'a' } }
+     */
+    function updateWith(object, path, updater, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
+    }
+
+    /**
+     * Creates an array of the own enumerable string keyed property values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.values(new Foo);
+     * // => [1, 2] (iteration order is not guaranteed)
+     *
+     * _.values('hi');
+     * // => ['h', 'i']
+     */
+    function values(object) {
+      return object ? baseValues(object, keys(object)) : [];
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable string keyed property
+     * values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.valuesIn(new Foo);
+     * // => [1, 2, 3] (iteration order is not guaranteed)
+     */
+    function valuesIn(object) {
+      return object == null ? [] : baseValues(object, keysIn(object));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Clamps `number` within the inclusive `lower` and `upper` bounds.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Number
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     * @example
+     *
+     * _.clamp(-10, -5, 5);
+     * // => -5
+     *
+     * _.clamp(10, -5, 5);
+     * // => 5
+     */
+    function clamp(number, lower, upper) {
+      if (upper === undefined) {
+        upper = lower;
+        lower = undefined;
+      }
+      if (upper !== undefined) {
+        upper = toNumber(upper);
+        upper = upper === upper ? upper : 0;
+      }
+      if (lower !== undefined) {
+        lower = toNumber(lower);
+        lower = lower === lower ? lower : 0;
+      }
+      return baseClamp(toNumber(number), lower, upper);
+    }
+
+    /**
+     * Checks if `n` is between `start` and up to, but not including, `end`. If
+     * `end` is not specified, it's set to `start` with `start` then set to `0`.
+     * If `start` is greater than `end` the params are swapped to support
+     * negative ranges.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.3.0
+     * @category Number
+     * @param {number} number The number to check.
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     * @see _.range, _.rangeRight
+     * @example
+     *
+     * _.inRange(3, 2, 4);
+     * // => true
+     *
+     * _.inRange(4, 8);
+     * // => true
+     *
+     * _.inRange(4, 2);
+     * // => false
+     *
+     * _.inRange(2, 2);
+     * // => false
+     *
+     * _.inRange(1.2, 2);
+     * // => true
+     *
+     * _.inRange(5.2, 4);
+     * // => false
+     *
+     * _.inRange(-3, -2, -6);
+     * // => true
+     */
+    function inRange(number, start, end) {
+      start = toNumber(start) || 0;
+      if (end === undefined) {
+        end = start;
+        start = 0;
+      } else {
+        end = toNumber(end) || 0;
+      }
+      number = toNumber(number);
+      return baseInRange(number, start, end);
+    }
+
+    /**
+     * Produces a random number between the inclusive `lower` and `upper` bounds.
+     * If only one argument is provided a number between `0` and the given number
+     * is returned. If `floating` is `true`, or either `lower` or `upper` are
+     * floats, a floating-point number is returned instead of an integer.
+     *
+     * **Note:** JavaScript follows the IEEE-754 standard for resolving
+     * floating-point values which can produce unexpected results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.7.0
+     * @category Number
+     * @param {number} [lower=0] The lower bound.
+     * @param {number} [upper=1] The upper bound.
+     * @param {boolean} [floating] Specify returning a floating-point number.
+     * @returns {number} Returns the random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(lower, upper, floating) {
+      if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+        upper = floating = undefined;
+      }
+      if (floating === undefined) {
+        if (typeof upper == 'boolean') {
+          floating = upper;
+          upper = undefined;
+        }
+        else if (typeof lower == 'boolean') {
+          floating = lower;
+          lower = undefined;
+        }
+      }
+      if (lower === undefined && upper === undefined) {
+        lower = 0;
+        upper = 1;
+      }
+      else {
+        lower = toNumber(lower) || 0;
+        if (upper === undefined) {
+          upper = lower;
+          lower = 0;
+        } else {
+          upper = toNumber(upper) || 0;
+        }
+      }
+      if (lower > upper) {
+        var temp = lower;
+        lower = upper;
+        upper = temp;
+      }
+      if (floating || lower % 1 || upper % 1) {
+        var rand = nativeRandom();
+        return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
+      }
+      return baseRandom(lower, upper);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the camel cased string.
+     * @example
+     *
+     * _.camelCase('Foo Bar');
+     * // => 'fooBar'
+     *
+     * _.camelCase('--foo-bar--');
+     * // => 'fooBar'
+     *
+     * _.camelCase('__FOO_BAR__');
+     * // => 'fooBar'
+     */
+    var camelCase = createCompounder(function(result, word, index) {
+      word = word.toLowerCase();
+      return result + (index ? capitalize(word) : word);
+    });
+
+    /**
+     * Converts the first character of `string` to upper case and the remaining
+     * to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to capitalize.
+     * @returns {string} Returns the capitalized string.
+     * @example
+     *
+     * _.capitalize('FRED');
+     * // => 'Fred'
+     */
+    function capitalize(string) {
+      return upperFirst(toString(string).toLowerCase());
+    }
+
+    /**
+     * Deburrs `string` by converting
+     * [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+     * to basic latin letters and removing
+     * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to deburr.
+     * @returns {string} Returns the deburred string.
+     * @example
+     *
+     * _.deburr('déjà vu');
+     * // => 'deja vu'
+     */
+    function deburr(string) {
+      string = toString(string);
+      return string && string.replace(reLatin1, deburrLetter).replace(reComboMark, '');
+    }
+
+    /**
+     * Checks if `string` ends with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to search.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=string.length] The position to search from.
+     * @returns {boolean} Returns `true` if `string` ends with `target`,
+     *  else `false`.
+     * @example
+     *
+     * _.endsWith('abc', 'c');
+     * // => true
+     *
+     * _.endsWith('abc', 'b');
+     * // => false
+     *
+     * _.endsWith('abc', 'b', 2);
+     * // => true
+     */
+    function endsWith(string, target, position) {
+      string = toString(string);
+      target = baseToString(target);
+
+      var length = string.length;
+      position = position === undefined
+        ? length
+        : baseClamp(toInteger(position), 0, length);
+
+      position -= target.length;
+      return position >= 0 && string.indexOf(target, position) == position;
+    }
+
+    /**
+     * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to
+     * their corresponding HTML entities.
+     *
+     * **Note:** No other characters are escaped. To escape additional
+     * characters use a third-party library like [_he_](https://mths.be/he).
+     *
+     * Though the ">" character is escaped for symmetry, characters like
+     * ">" and "/" don't need escaping in HTML and have no special meaning
+     * unless they're part of a tag or unquoted attribute value. See
+     * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+     * (under "semi-related fun fact") for more details.
+     *
+     * Backticks are escaped because in IE < 9, they can break out of
+     * attribute values or HTML comments. See [#59](https://html5sec.org/#59),
+     * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
+     * [#133](https://html5sec.org/#133) of the
+     * [HTML5 Security Cheatsheet](https://html5sec.org/) for more details.
+     *
+     * When working with HTML you should always
+     * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
+     * XSS vectors.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('fred, barney, & pebbles');
+     * // => 'fred, barney, &amp; pebbles'
+     */
+    function escape(string) {
+      string = toString(string);
+      return (string && reHasUnescapedHtml.test(string))
+        ? string.replace(reUnescapedHtml, escapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+     * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escapeRegExp('[lodash](https://lodash.com/)');
+     * // => '\[lodash\]\(https://lodash\.com/\)'
+     */
+    function escapeRegExp(string) {
+      string = toString(string);
+      return (string && reHasRegExpChar.test(string))
+        ? string.replace(reRegExpChar, '\\$&')
+        : string;
+    }
+
+    /**
+     * Converts `string` to
+     * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the kebab cased string.
+     * @example
+     *
+     * _.kebabCase('Foo Bar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('fooBar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('__FOO_BAR__');
+     * // => 'foo-bar'
+     */
+    var kebabCase = createCompounder(function(result, word, index) {
+      return result + (index ? '-' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts `string`, as space separated words, to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the lower cased string.
+     * @example
+     *
+     * _.lowerCase('--Foo-Bar--');
+     * // => 'foo bar'
+     *
+     * _.lowerCase('fooBar');
+     * // => 'foo bar'
+     *
+     * _.lowerCase('__FOO_BAR__');
+     * // => 'foo bar'
+     */
+    var lowerCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts the first character of `string` to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.lowerFirst('Fred');
+     * // => 'fred'
+     *
+     * _.lowerFirst('FRED');
+     * // => 'fRED'
+     */
+    var lowerFirst = createCaseFirst('toLowerCase');
+
+    /**
+     * Pads `string` on the left and right sides if it's shorter than `length`.
+     * Padding characters are truncated if they can't be evenly divided by `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.pad('abc', 8);
+     * // => '  abc   '
+     *
+     * _.pad('abc', 8, '_-');
+     * // => '_-abc_-_'
+     *
+     * _.pad('abc', 3);
+     * // => 'abc'
+     */
+    function pad(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      if (!length || strLength >= length) {
+        return string;
+      }
+      var mid = (length - strLength) / 2;
+      return (
+        createPadding(nativeFloor(mid), chars) +
+        string +
+        createPadding(nativeCeil(mid), chars)
+      );
+    }
+
+    /**
+     * Pads `string` on the right side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padEnd('abc', 6);
+     * // => 'abc   '
+     *
+     * _.padEnd('abc', 6, '_-');
+     * // => 'abc_-_'
+     *
+     * _.padEnd('abc', 3);
+     * // => 'abc'
+     */
+    function padEnd(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      return (length && strLength < length)
+        ? (string + createPadding(length - strLength, chars))
+        : string;
+    }
+
+    /**
+     * Pads `string` on the left side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padStart('abc', 6);
+     * // => '   abc'
+     *
+     * _.padStart('abc', 6, '_-');
+     * // => '_-_abc'
+     *
+     * _.padStart('abc', 3);
+     * // => 'abc'
+     */
+    function padStart(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      return (length && strLength < length)
+        ? (createPadding(length - strLength, chars) + string)
+        : string;
+    }
+
+    /**
+     * Converts `string` to an integer of the specified radix. If `radix` is
+     * `undefined` or `0`, a `radix` of `10` is used unless `value` is a
+     * hexadecimal, in which case a `radix` of `16` is used.
+     *
+     * **Note:** This method aligns with the
+     * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category String
+     * @param {string} string The string to convert.
+     * @param {number} [radix=10] The radix to interpret `value` by.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     *
+     * _.map(['6', '08', '10'], _.parseInt);
+     * // => [6, 8, 10]
+     */
+    function parseInt(string, radix, guard) {
+      // Chrome fails to trim leading <BOM> whitespace characters.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=3109 for more details.
+      if (guard || radix == null) {
+        radix = 0;
+      } else if (radix) {
+        radix = +radix;
+      }
+      string = toString(string).replace(reTrim, '');
+      return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10));
+    }
+
+    /**
+     * Repeats the given string `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to repeat.
+     * @param {number} [n=1] The number of times to repeat the string.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the repeated string.
+     * @example
+     *
+     * _.repeat('*', 3);
+     * // => '***'
+     *
+     * _.repeat('abc', 2);
+     * // => 'abcabc'
+     *
+     * _.repeat('abc', 0);
+     * // => ''
+     */
+    function repeat(string, n, guard) {
+      if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = toInteger(n);
+      }
+      return baseRepeat(toString(string), n);
+    }
+
+    /**
+     * Replaces matches for `pattern` in `string` with `replacement`.
+     *
+     * **Note:** This method is based on
+     * [`String#replace`](https://mdn.io/String/replace).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to modify.
+     * @param {RegExp|string} pattern The pattern to replace.
+     * @param {Function|string} replacement The match replacement.
+     * @returns {string} Returns the modified string.
+     * @example
+     *
+     * _.replace('Hi Fred', 'Fred', 'Barney');
+     * // => 'Hi Barney'
+     */
+    function replace() {
+      var args = arguments,
+          string = toString(args[0]);
+
+      return args.length < 3 ? string : nativeReplace.call(string, args[1], args[2]);
+    }
+
+    /**
+     * Converts `string` to
+     * [snake case](https://en.wikipedia.org/wiki/Snake_case).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the snake cased string.
+     * @example
+     *
+     * _.snakeCase('Foo Bar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('fooBar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('--FOO-BAR--');
+     * // => 'foo_bar'
+     */
+    var snakeCase = createCompounder(function(result, word, index) {
+      return result + (index ? '_' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Splits `string` by `separator`.
+     *
+     * **Note:** This method is based on
+     * [`String#split`](https://mdn.io/String/split).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to split.
+     * @param {RegExp|string} separator The separator pattern to split by.
+     * @param {number} [limit] The length to truncate results to.
+     * @returns {Array} Returns the new array of string segments.
+     * @example
+     *
+     * _.split('a-b-c', '-', 2);
+     * // => ['a', 'b']
+     */
+    function split(string, separator, limit) {
+      if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
+        separator = limit = undefined;
+      }
+      limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0;
+      if (!limit) {
+        return [];
+      }
+      string = toString(string);
+      if (string && (
+            typeof separator == 'string' ||
+            (separator != null && !isRegExp(separator))
+          )) {
+        separator = baseToString(separator);
+        if (separator == '' && reHasComplexSymbol.test(string)) {
+          return castSlice(stringToArray(string), 0, limit);
+        }
+      }
+      return nativeSplit.call(string, separator, limit);
+    }
+
+    /**
+     * Converts `string` to
+     * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.1.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the start cased string.
+     * @example
+     *
+     * _.startCase('--foo-bar--');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('fooBar');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('__FOO_BAR__');
+     * // => 'FOO BAR'
+     */
+    var startCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + upperFirst(word);
+    });
+
+    /**
+     * Checks if `string` starts with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to search.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=0] The position to search from.
+     * @returns {boolean} Returns `true` if `string` starts with `target`,
+     *  else `false`.
+     * @example
+     *
+     * _.startsWith('abc', 'a');
+     * // => true
+     *
+     * _.startsWith('abc', 'b');
+     * // => false
+     *
+     * _.startsWith('abc', 'b', 1);
+     * // => true
+     */
+    function startsWith(string, target, position) {
+      string = toString(string);
+      position = baseClamp(toInteger(position), 0, string.length);
+      return string.lastIndexOf(baseToString(target), position) == position;
+    }
+
+    /**
+     * Creates a compiled template function that can interpolate data properties
+     * in "interpolate" delimiters, HTML-escape interpolated data properties in
+     * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+     * properties may be accessed as free variables in the template. If a setting
+     * object is given, it takes precedence over `_.templateSettings` values.
+     *
+     * **Note:** In the development build `_.template` utilizes
+     * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+     * for easier debugging.
+     *
+     * For more information on precompiling templates see
+     * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+     *
+     * For more information on Chrome extension sandboxes see
+     * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The template string.
+     * @param {Object} [options={}] The options object.
+     * @param {RegExp} [options.escape=_.templateSettings.escape]
+     *  The HTML "escape" delimiter.
+     * @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
+     *  The "evaluate" delimiter.
+     * @param {Object} [options.imports=_.templateSettings.imports]
+     *  An object to import into the template as free variables.
+     * @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
+     *  The "interpolate" delimiter.
+     * @param {string} [options.sourceURL='lodash.templateSources[n]']
+     *  The sourceURL of the compiled template.
+     * @param {string} [options.variable='obj']
+     *  The data object variable name.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the compiled template function.
+     * @example
+     *
+     * // Use the "interpolate" delimiter to create a compiled template.
+     * var compiled = _.template('hello <%= user %>!');
+     * compiled({ 'user': 'fred' });
+     * // => 'hello fred!'
+     *
+     * // Use the HTML "escape" delimiter to escape data property values.
+     * var compiled = _.template('<b><%- value %></b>');
+     * compiled({ 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+     * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // Use the internal `print` function in "evaluate" delimiters.
+     * var compiled = _.template('<% print("hello " + user); %>!');
+     * compiled({ 'user': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // Use the ES delimiter as an alternative to the default "interpolate" delimiter.
+     * var compiled = _.template('hello ${ user }!');
+     * compiled({ 'user': 'pebbles' });
+     * // => 'hello pebbles!'
+     *
+     * // Use custom template delimiters.
+     * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+     * var compiled = _.template('hello {{ user }}!');
+     * compiled({ 'user': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // Use backslashes to treat delimiters as plain text.
+     * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+     * compiled({ 'value': 'ignored' });
+     * // => '<%- value %>'
+     *
+     * // Use the `imports` option to import `jQuery` as `jq`.
+     * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+     * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // Use the `sourceURL` option to specify a custom sourceURL for the template.
+     * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
+     *
+     * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+     * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     * //   var __t, __p = '';
+     * //   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+     * //   return __p;
+     * // }
+     *
+     * // Use the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and stack traces.
+     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(string, options, guard) {
+      // Based on John Resig's `tmpl` implementation
+      // (http://ejohn.org/blog/javascript-micro-templating/)
+      // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+      var settings = lodash.templateSettings;
+
+      if (guard && isIterateeCall(string, options, guard)) {
+        options = undefined;
+      }
+      string = toString(string);
+      options = assignInWith({}, options, settings, assignInDefaults);
+
+      var imports = assignInWith({}, options.imports, settings.imports, assignInDefaults),
+          importsKeys = keys(imports),
+          importsValues = baseValues(imports, importsKeys);
+
+      var isEscaping,
+          isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // Compile the regexp to match each delimiter.
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      // Use a sourceURL for easier debugging.
+      var sourceURL = '//# sourceURL=' +
+        ('sourceURL' in options
+          ? options.sourceURL
+          : ('lodash.templateSources[' + (++templateCounter) + ']')
+        ) + '\n';
+
+      string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // Escape characters that can't be included in string literals.
+        source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // Replace delimiters with snippets.
+        if (escapeValue) {
+          isEscaping = true;
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // The JS engine embedded in Adobe products needs `match` returned in
+        // order to produce the correct `offset` value.
+        return match;
+      });
+
+      source += "';\n";
+
+      // If `variable` is not specified wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain.
+      var variable = options.variable;
+      if (!variable) {
+        source = 'with (obj) {\n' + source + '\n}\n';
+      }
+      // Cleanup code by stripping empty strings.
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // Frame code as the function body.
+      source = 'function(' + (variable || 'obj') + ') {\n' +
+        (variable
+          ? ''
+          : 'obj || (obj = {});\n'
+        ) +
+        "var __t, __p = ''" +
+        (isEscaping
+           ? ', __e = _.escape'
+           : ''
+        ) +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      var result = attempt(function() {
+        return Function(importsKeys, sourceURL + 'return ' + source)
+          .apply(undefined, importsValues);
+      });
+
+      // Provide the compiled function's source by its `toString` method or
+      // the `source` property as a convenience for inlining compiled templates.
+      result.source = source;
+      if (isError(result)) {
+        throw result;
+      }
+      return result;
+    }
+
+    /**
+     * Converts `string`, as a whole, to lower case just like
+     * [String#toLowerCase](https://mdn.io/toLowerCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the lower cased string.
+     * @example
+     *
+     * _.toLower('--Foo-Bar--');
+     * // => '--foo-bar--'
+     *
+     * _.toLower('fooBar');
+     * // => 'foobar'
+     *
+     * _.toLower('__FOO_BAR__');
+     * // => '__foo_bar__'
+     */
+    function toLower(value) {
+      return toString(value).toLowerCase();
+    }
+
+    /**
+     * Converts `string`, as a whole, to upper case just like
+     * [String#toUpperCase](https://mdn.io/toUpperCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the upper cased string.
+     * @example
+     *
+     * _.toUpper('--foo-bar--');
+     * // => '--FOO-BAR--'
+     *
+     * _.toUpper('fooBar');
+     * // => 'FOOBAR'
+     *
+     * _.toUpper('__foo_bar__');
+     * // => '__FOO_BAR__'
+     */
+    function toUpper(value) {
+      return toString(value).toUpperCase();
+    }
+
+    /**
+     * Removes leading and trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trim('  abc  ');
+     * // => 'abc'
+     *
+     * _.trim('-_-abc-_-', '_-');
+     * // => 'abc'
+     *
+     * _.map(['  foo  ', '  bar  '], _.trim);
+     * // => ['foo', 'bar']
+     */
+    function trim(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrim, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          chrSymbols = stringToArray(chars),
+          start = charsStartIndex(strSymbols, chrSymbols),
+          end = charsEndIndex(strSymbols, chrSymbols) + 1;
+
+      return castSlice(strSymbols, start, end).join('');
+    }
+
+    /**
+     * Removes trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimEnd('  abc  ');
+     * // => '  abc'
+     *
+     * _.trimEnd('-_-abc-_-', '_-');
+     * // => '-_-abc'
+     */
+    function trimEnd(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrimEnd, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;
+
+      return castSlice(strSymbols, 0, end).join('');
+    }
+
+    /**
+     * Removes leading whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimStart('  abc  ');
+     * // => 'abc  '
+     *
+     * _.trimStart('-_-abc-_-', '_-');
+     * // => 'abc-_-'
+     */
+    function trimStart(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrimStart, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          start = charsStartIndex(strSymbols, stringToArray(chars));
+
+      return castSlice(strSymbols, start).join('');
+    }
+
+    /**
+     * Truncates `string` if it's longer than the given maximum string length.
+     * The last characters of the truncated string are replaced with the omission
+     * string which defaults to "...".
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to truncate.
+     * @param {Object} [options={}] The options object.
+     * @param {number} [options.length=30] The maximum string length.
+     * @param {string} [options.omission='...'] The string to indicate text is omitted.
+     * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+     * @returns {string} Returns the truncated string.
+     * @example
+     *
+     * _.truncate('hi-diddly-ho there, neighborino');
+     * // => 'hi-diddly-ho there, neighbo...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': ' '
+     * });
+     * // => 'hi-diddly-ho there,...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': /,? +/
+     * });
+     * // => 'hi-diddly-ho there...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'omission': ' [...]'
+     * });
+     * // => 'hi-diddly-ho there, neig [...]'
+     */
+    function truncate(string, options) {
+      var length = DEFAULT_TRUNC_LENGTH,
+          omission = DEFAULT_TRUNC_OMISSION;
+
+      if (isObject(options)) {
+        var separator = 'separator' in options ? options.separator : separator;
+        length = 'length' in options ? toInteger(options.length) : length;
+        omission = 'omission' in options ? baseToString(options.omission) : omission;
+      }
+      string = toString(string);
+
+      var strLength = string.length;
+      if (reHasComplexSymbol.test(string)) {
+        var strSymbols = stringToArray(string);
+        strLength = strSymbols.length;
+      }
+      if (length >= strLength) {
+        return string;
+      }
+      var end = length - stringSize(omission);
+      if (end < 1) {
+        return omission;
+      }
+      var result = strSymbols
+        ? castSlice(strSymbols, 0, end).join('')
+        : string.slice(0, end);
+
+      if (separator === undefined) {
+        return result + omission;
+      }
+      if (strSymbols) {
+        end += (result.length - end);
+      }
+      if (isRegExp(separator)) {
+        if (string.slice(end).search(separator)) {
+          var match,
+              substring = result;
+
+          if (!separator.global) {
+            separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
+          }
+          separator.lastIndex = 0;
+          while ((match = separator.exec(substring))) {
+            var newEnd = match.index;
+          }
+          result = result.slice(0, newEnd === undefined ? end : newEnd);
+        }
+      } else if (string.indexOf(baseToString(separator), end) != end) {
+        var index = result.lastIndexOf(separator);
+        if (index > -1) {
+          result = result.slice(0, index);
+        }
+      }
+      return result + omission;
+    }
+
+    /**
+     * The inverse of `_.escape`; this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`, and `&#96;` in `string` to
+     * their corresponding characters.
+     *
+     * **Note:** No other HTML entities are unescaped. To unescape additional
+     * HTML entities use a third-party library like [_he_](https://mths.be/he).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.6.0
+     * @category String
+     * @param {string} [string=''] The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('fred, barney, &amp; pebbles');
+     * // => 'fred, barney, & pebbles'
+     */
+    function unescape(string) {
+      string = toString(string);
+      return (string && reHasEscapedHtml.test(string))
+        ? string.replace(reEscapedHtml, unescapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Converts `string`, as space separated words, to upper case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the upper cased string.
+     * @example
+     *
+     * _.upperCase('--foo-bar');
+     * // => 'FOO BAR'
+     *
+     * _.upperCase('fooBar');
+     * // => 'FOO BAR'
+     *
+     * _.upperCase('__foo_bar__');
+     * // => 'FOO BAR'
+     */
+    var upperCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + word.toUpperCase();
+    });
+
+    /**
+     * Converts the first character of `string` to upper case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.upperFirst('fred');
+     * // => 'Fred'
+     *
+     * _.upperFirst('FRED');
+     * // => 'FRED'
+     */
+    var upperFirst = createCaseFirst('toUpperCase');
+
+    /**
+     * Splits `string` into an array of its words.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to inspect.
+     * @param {RegExp|string} [pattern] The pattern to match words.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the words of `string`.
+     * @example
+     *
+     * _.words('fred, barney, & pebbles');
+     * // => ['fred', 'barney', 'pebbles']
+     *
+     * _.words('fred, barney, & pebbles', /[^, ]+/g);
+     * // => ['fred', 'barney', '&', 'pebbles']
+     */
+    function words(string, pattern, guard) {
+      string = toString(string);
+      pattern = guard ? undefined : pattern;
+
+      if (pattern === undefined) {
+        pattern = reHasComplexWord.test(string) ? reComplexWord : reBasicWord;
+      }
+      return string.match(pattern) || [];
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Attempts to invoke `func`, returning either the result or the caught error
+     * object. Any additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Function} func The function to attempt.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {*} Returns the `func` result or error object.
+     * @example
+     *
+     * // Avoid throwing errors for invalid selectors.
+     * var elements = _.attempt(function(selector) {
+     *   return document.querySelectorAll(selector);
+     * }, '>_>');
+     *
+     * if (_.isError(elements)) {
+     *   elements = [];
+     * }
+     */
+    var attempt = rest(function(func, args) {
+      try {
+        return apply(func, undefined, args);
+      } catch (e) {
+        return isError(e) ? e : new Error(e);
+      }
+    });
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method.
+     *
+     * **Note:** This method doesn't set the "length" property of bound functions.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...(string|string[])} methodNames The object method names to bind.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'onClick': function() {
+     *     console.log('clicked ' + this.label);
+     *   }
+     * };
+     *
+     * _.bindAll(view, 'onClick');
+     * jQuery(element).on('click', view.onClick);
+     * // => Logs 'clicked docs' when clicked.
+     */
+    var bindAll = rest(function(object, methodNames) {
+      arrayEach(baseFlatten(methodNames, 1), function(key) {
+        key = toKey(key);
+        object[key] = bind(object[key], object);
+      });
+      return object;
+    });
+
+    /**
+     * Creates a function that iterates over `pairs` and invokes the corresponding
+     * function of the first predicate to return truthy. The predicate-function
+     * pairs are invoked with the `this` binding and arguments of the created
+     * function.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {Array} pairs The predicate-function pairs.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.cond([
+     *   [_.matches({ 'a': 1 }),           _.constant('matches A')],
+     *   [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+     *   [_.constant(true),                _.constant('no match')]
+     * ]);
+     *
+     * func({ 'a': 1, 'b': 2 });
+     * // => 'matches A'
+     *
+     * func({ 'a': 0, 'b': 1 });
+     * // => 'matches B'
+     *
+     * func({ 'a': '1', 'b': '2' });
+     * // => 'no match'
+     */
+    function cond(pairs) {
+      var length = pairs ? pairs.length : 0,
+          toIteratee = getIteratee();
+
+      pairs = !length ? [] : arrayMap(pairs, function(pair) {
+        if (typeof pair[1] != 'function') {
+          throw new TypeError(FUNC_ERROR_TEXT);
+        }
+        return [toIteratee(pair[0]), pair[1]];
+      });
+
+      return rest(function(args) {
+        var index = -1;
+        while (++index < length) {
+          var pair = pairs[index];
+          if (apply(pair[0], this, args)) {
+            return apply(pair[1], this, args);
+          }
+        }
+      });
+    }
+
+    /**
+     * Creates a function that invokes the predicate properties of `source` with
+     * the corresponding property values of a given object, returning `true` if
+     * all predicates return truthy, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.filter(users, _.conforms({ 'age': _.partial(_.gt, _, 38) }));
+     * // => [{ 'user': 'fred', 'age': 40 }]
+     */
+    function conforms(source) {
+      return baseConforms(baseClone(source, true));
+    }
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Util
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var getter = _.constant(object);
+     *
+     * getter() === object;
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * Creates a function that returns the result of invoking the given functions
+     * with the `this` binding of the created function, where each successive
+     * invocation is supplied the return value of the previous.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {...(Function|Function[])} [funcs] Functions to invoke.
+     * @returns {Function} Returns the new function.
+     * @see _.flowRight
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flow(_.add, square);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flow = createFlow();
+
+    /**
+     * This method is like `_.flow` except that it creates a function that
+     * invokes the given functions from right to left.
+     *
+     * @static
+     * @since 3.0.0
+     * @memberOf _
+     * @category Util
+     * @param {...(Function|Function[])} [funcs] Functions to invoke.
+     * @returns {Function} Returns the new function.
+     * @see _.flow
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flowRight(square, _.add);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flowRight = createFlow(true);
+
+    /**
+     * This method returns the first argument given to it.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * _.identity(object) === object;
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Creates a function that invokes `func` with the arguments of the created
+     * function. If `func` is a property name, the created function returns the
+     * property value for a given element. If `func` is an array or object, the
+     * created function returns `true` for elements that contain the equivalent
+     * source properties, otherwise it returns `false`.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Util
+     * @param {*} [func=_.identity] The value to convert to a callback.
+     * @returns {Function} Returns the callback.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+     * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, _.iteratee(['user', 'fred']));
+     * // => [{ 'user': 'fred', 'age': 40 }]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, _.iteratee('user'));
+     * // => ['barney', 'fred']
+     *
+     * // Create custom iteratee shorthands.
+     * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+     *   return !_.isRegExp(func) ? iteratee(func) : function(string) {
+     *     return func.test(string);
+     *   };
+     * });
+     *
+     * _.filter(['abc', 'def'], /ef/);
+     * // => ['def']
+     */
+    function iteratee(func) {
+      return baseIteratee(typeof func == 'function' ? func : baseClone(func, true));
+    }
+
+    /**
+     * Creates a function that performs a partial deep comparison between a given
+     * object and `source`, returning `true` if the given object has equivalent
+     * property values, else `false`. The created function is equivalent to
+     * `_.isMatch` with a `source` partially applied.
+     *
+     * **Note:** This method supports comparing the same values as `_.isEqual`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, _.matches({ 'age': 40, 'active': false }));
+     * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
+     */
+    function matches(source) {
+      return baseMatches(baseClone(source, true));
+    }
+
+    /**
+     * Creates a function that performs a partial deep comparison between the
+     * value at `path` of a given object to `srcValue`, returning `true` if the
+     * object value is equivalent, else `false`.
+     *
+     * **Note:** This method supports comparing the same values as `_.isEqual`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Util
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * _.find(users, _.matchesProperty('user', 'fred'));
+     * // => { 'user': 'fred' }
+     */
+    function matchesProperty(path, srcValue) {
+      return baseMatchesProperty(path, baseClone(srcValue, true));
+    }
+
+    /**
+     * Creates a function that invokes the method at `path` of a given object.
+     * Any additional arguments are provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Util
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': _.constant(2) } },
+     *   { 'a': { 'b': _.constant(1) } }
+     * ];
+     *
+     * _.map(objects, _.method('a.b'));
+     * // => [2, 1]
+     *
+     * _.map(objects, _.method(['a', 'b']));
+     * // => [2, 1]
+     */
+    var method = rest(function(path, args) {
+      return function(object) {
+        return baseInvoke(object, path, args);
+      };
+    });
+
+    /**
+     * The opposite of `_.method`; this method creates a function that invokes
+     * the method at a given path of `object`. Any additional arguments are
+     * provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Util
+     * @param {Object} object The object to query.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var array = _.times(3, _.constant),
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+     * // => [2, 0]
+     */
+    var methodOf = rest(function(object, args) {
+      return function(path) {
+        return baseInvoke(object, path, args);
+      };
+    });
+
+    /**
+     * Adds all own enumerable string keyed function properties of a source
+     * object to the destination object. If `object` is a function, then methods
+     * are added to its prototype as well.
+     *
+     * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+     * avoid conflicts caused by modifying the original.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {Function|Object} [object=lodash] The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
+     * @returns {Function|Object} Returns `object`.
+     * @example
+     *
+     * function vowels(string) {
+     *   return _.filter(string, function(v) {
+     *     return /[aeiou]/i.test(v);
+     *   });
+     * }
+     *
+     * _.mixin({ 'vowels': vowels });
+     * _.vowels('fred');
+     * // => ['e']
+     *
+     * _('fred').vowels().value();
+     * // => ['e']
+     *
+     * _.mixin({ 'vowels': vowels }, { 'chain': false });
+     * _('fred').vowels();
+     * // => ['e']
+     */
+    function mixin(object, source, options) {
+      var props = keys(source),
+          methodNames = baseFunctions(source, props);
+
+      if (options == null &&
+          !(isObject(source) && (methodNames.length || !props.length))) {
+        options = source;
+        source = object;
+        object = this;
+        methodNames = baseFunctions(source, keys(source));
+      }
+      var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
+          isFunc = isFunction(object);
+
+      arrayEach(methodNames, function(methodName) {
+        var func = source[methodName];
+        object[methodName] = func;
+        if (isFunc) {
+          object.prototype[methodName] = function() {
+            var chainAll = this.__chain__;
+            if (chain || chainAll) {
+              var result = object(this.__wrapped__),
+                  actions = result.__actions__ = copyArray(this.__actions__);
+
+              actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+              result.__chain__ = chainAll;
+              return result;
+            }
+            return func.apply(object, arrayPush([this.value()], arguments));
+          };
+        }
+      });
+
+      return object;
+    }
+
+    /**
+     * Reverts the `_` variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      if (root._ === this) {
+        root._ = oldDash;
+      }
+      return this;
+    }
+
+    /**
+     * A no-operation function that returns `undefined` regardless of the
+     * arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.3.0
+     * @category Util
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * _.noop(object) === undefined;
+     * // => true
+     */
+    function noop() {
+      // No operation performed.
+    }
+
+    /**
+     * Creates a function that returns its nth argument. If `n` is negative,
+     * the nth argument from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {number} [n=0] The index of the argument to return.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.nthArg(1);
+     * func('a', 'b', 'c', 'd');
+     * // => 'b'
+     *
+     * var func = _.nthArg(-2);
+     * func('a', 'b', 'c', 'd');
+     * // => 'c'
+     */
+    function nthArg(n) {
+      n = toInteger(n);
+      return rest(function(args) {
+        return baseNth(args, n);
+      });
+    }
+
+    /**
+     * Creates a function that invokes `iteratees` with the arguments it receives
+     * and returns their results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [iteratees=[_.identity]] The iteratees to invoke.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.over(Math.max, Math.min);
+     *
+     * func(1, 2, 3, 4);
+     * // => [4, 1]
+     */
+    var over = createOver(arrayMap);
+
+    /**
+     * Creates a function that checks if **all** of the `predicates` return
+     * truthy when invoked with the arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [predicates=[_.identity]] The predicates to check.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.overEvery(Boolean, isFinite);
+     *
+     * func('1');
+     * // => true
+     *
+     * func(null);
+     * // => false
+     *
+     * func(NaN);
+     * // => false
+     */
+    var overEvery = createOver(arrayEvery);
+
+    /**
+     * Creates a function that checks if **any** of the `predicates` return
+     * truthy when invoked with the arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [predicates=[_.identity]] The predicates to check.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.overSome(Boolean, isFinite);
+     *
+     * func('1');
+     * // => true
+     *
+     * func(null);
+     * // => true
+     *
+     * func(NaN);
+     * // => false
+     */
+    var overSome = createOver(arraySome);
+
+    /**
+     * Creates a function that returns the value at `path` of a given object.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Util
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': 2 } },
+     *   { 'a': { 'b': 1 } }
+     * ];
+     *
+     * _.map(objects, _.property('a.b'));
+     * // => [2, 1]
+     *
+     * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
+     * // => [1, 2]
+     */
+    function property(path) {
+      return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
+    }
+
+    /**
+     * The opposite of `_.property`; this method creates a function that returns
+     * the value at a given path of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Object} object The object to query.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var array = [0, 1, 2],
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+     * // => [2, 0]
+     */
+    function propertyOf(object) {
+      return function(path) {
+        return object == null ? undefined : baseGet(object, path);
+      };
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to, but not including, `end`. A step of `-1` is used if a negative
+     * `start` is specified without an `end` or `step`. If `end` is not specified,
+     * it's set to `start` with `start` then set to `0`.
+     *
+     * **Note:** JavaScript follows the IEEE-754 standard for resolving
+     * floating-point values which can produce unexpected results.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @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 decrement by.
+     * @returns {Array} Returns the new array of numbers.
+     * @see _.inRange, _.rangeRight
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(-4);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    var range = createRange();
+
+    /**
+     * This method is like `_.range` except that it populates values in
+     * descending order.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @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 decrement by.
+     * @returns {Array} Returns the new array of numbers.
+     * @see _.inRange, _.range
+     * @example
+     *
+     * _.rangeRight(4);
+     * // => [3, 2, 1, 0]
+     *
+     * _.rangeRight(-4);
+     * // => [-3, -2, -1, 0]
+     *
+     * _.rangeRight(1, 5);
+     * // => [4, 3, 2, 1]
+     *
+     * _.rangeRight(0, 20, 5);
+     * // => [15, 10, 5, 0]
+     *
+     * _.rangeRight(0, -4, -1);
+     * // => [-3, -2, -1, 0]
+     *
+     * _.rangeRight(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.rangeRight(0);
+     * // => []
+     */
+    var rangeRight = createRange(true);
+
+    /**
+     * Invokes the iteratee `n` times, returning an array of the results of
+     * each invocation. The iteratee is invoked with one argument; (index).
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {number} n The number of times to invoke `iteratee`.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.times(3, String);
+     * // => ['0', '1', '2']
+     *
+     *  _.times(4, _.constant(true));
+     * // => [true, true, true, true]
+     */
+    function times(n, iteratee) {
+      n = toInteger(n);
+      if (n < 1 || n > MAX_SAFE_INTEGER) {
+        return [];
+      }
+      var index = MAX_ARRAY_LENGTH,
+          length = nativeMin(n, MAX_ARRAY_LENGTH);
+
+      iteratee = getIteratee(iteratee);
+      n -= MAX_ARRAY_LENGTH;
+
+      var result = baseTimes(length, iteratee);
+      while (++index < n) {
+        iteratee(index);
+      }
+      return result;
+    }
+
+    /**
+     * Converts `value` to a property path array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the new property path array.
+     * @example
+     *
+     * _.toPath('a.b.c');
+     * // => ['a', 'b', 'c']
+     *
+     * _.toPath('a[0].b.c');
+     * // => ['a', '0', 'b', 'c']
+     *
+     * var path = ['a', 'b', 'c'],
+     *     newPath = _.toPath(path);
+     *
+     * console.log(newPath);
+     * // => ['a', 'b', 'c']
+     *
+     * console.log(path === newPath);
+     * // => false
+     */
+    function toPath(value) {
+      if (isArray(value)) {
+        return arrayMap(value, toKey);
+      }
+      return isSymbol(value) ? [value] : copyArray(stringToPath(value));
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is given, the ID is appended to it.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {string} [prefix=''] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return toString(prefix) + id;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Adds two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.4.0
+     * @category Math
+     * @param {number} augend The first number in an addition.
+     * @param {number} addend The second number in an addition.
+     * @returns {number} Returns the total.
+     * @example
+     *
+     * _.add(6, 4);
+     * // => 10
+     */
+    var add = createMathOperation(function(augend, addend) {
+      return augend + addend;
+    });
+
+    /**
+     * Computes `number` rounded up to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round up.
+     * @param {number} [precision=0] The precision to round up to.
+     * @returns {number} Returns the rounded up number.
+     * @example
+     *
+     * _.ceil(4.006);
+     * // => 5
+     *
+     * _.ceil(6.004, 2);
+     * // => 6.01
+     *
+     * _.ceil(6040, -2);
+     * // => 6100
+     */
+    var ceil = createRound('ceil');
+
+    /**
+     * Divide two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {number} dividend The first number in a division.
+     * @param {number} divisor The second number in a division.
+     * @returns {number} Returns the quotient.
+     * @example
+     *
+     * _.divide(6, 4);
+     * // => 1.5
+     */
+    var divide = createMathOperation(function(dividend, divisor) {
+      return dividend / divisor;
+    });
+
+    /**
+     * Computes `number` rounded down to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round down.
+     * @param {number} [precision=0] The precision to round down to.
+     * @returns {number} Returns the rounded down number.
+     * @example
+     *
+     * _.floor(4.006);
+     * // => 4
+     *
+     * _.floor(0.046, 2);
+     * // => 0.04
+     *
+     * _.floor(4060, -2);
+     * // => 4000
+     */
+    var floor = createRound('floor');
+
+    /**
+     * Computes the maximum value of `array`. If `array` is empty or falsey,
+     * `undefined` is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * _.max([]);
+     * // => undefined
+     */
+    function max(array) {
+      return (array && array.length)
+        ? baseExtremum(array, identity, baseGt)
+        : undefined;
+    }
+
+    /**
+     * This method is like `_.max` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * the value is ranked. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * var objects = [{ 'n': 1 }, { 'n': 2 }];
+     *
+     * _.maxBy(objects, function(o) { return o.n; });
+     * // => { 'n': 2 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.maxBy(objects, 'n');
+     * // => { 'n': 2 }
+     */
+    function maxBy(array, iteratee) {
+      return (array && array.length)
+        ? baseExtremum(array, getIteratee(iteratee), baseGt)
+        : undefined;
+    }
+
+    /**
+     * Computes the mean of the values in `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {number} Returns the mean.
+     * @example
+     *
+     * _.mean([4, 2, 8, 6]);
+     * // => 5
+     */
+    function mean(array) {
+      return baseMean(array, identity);
+    }
+
+    /**
+     * This method is like `_.mean` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the value to be averaged.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the mean.
+     * @example
+     *
+     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+     *
+     * _.meanBy(objects, function(o) { return o.n; });
+     * // => 5
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.meanBy(objects, 'n');
+     * // => 5
+     */
+    function meanBy(array, iteratee) {
+      return baseMean(array, getIteratee(iteratee));
+    }
+
+    /**
+     * Computes the minimum value of `array`. If `array` is empty or falsey,
+     * `undefined` is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * _.min([]);
+     * // => undefined
+     */
+    function min(array) {
+      return (array && array.length)
+        ? baseExtremum(array, identity, baseLt)
+        : undefined;
+    }
+
+    /**
+     * This method is like `_.min` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * the value is ranked. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * var objects = [{ 'n': 1 }, { 'n': 2 }];
+     *
+     * _.minBy(objects, function(o) { return o.n; });
+     * // => { 'n': 1 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.minBy(objects, 'n');
+     * // => { 'n': 1 }
+     */
+    function minBy(array, iteratee) {
+      return (array && array.length)
+        ? baseExtremum(array, getIteratee(iteratee), baseLt)
+        : undefined;
+    }
+
+    /**
+     * Multiply two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {number} multiplier The first number in a multiplication.
+     * @param {number} multiplicand The second number in a multiplication.
+     * @returns {number} Returns the product.
+     * @example
+     *
+     * _.multiply(6, 4);
+     * // => 24
+     */
+    var multiply = createMathOperation(function(multiplier, multiplicand) {
+      return multiplier * multiplicand;
+    });
+
+    /**
+     * Computes `number` rounded to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round.
+     * @param {number} [precision=0] The precision to round to.
+     * @returns {number} Returns the rounded number.
+     * @example
+     *
+     * _.round(4.006);
+     * // => 4
+     *
+     * _.round(4.006, 2);
+     * // => 4.01
+     *
+     * _.round(4060, -2);
+     * // => 4100
+     */
+    var round = createRound('round');
+
+    /**
+     * Subtract two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {number} minuend The first number in a subtraction.
+     * @param {number} subtrahend The second number in a subtraction.
+     * @returns {number} Returns the difference.
+     * @example
+     *
+     * _.subtract(6, 4);
+     * // => 2
+     */
+    var subtract = createMathOperation(function(minuend, subtrahend) {
+      return minuend - subtrahend;
+    });
+
+    /**
+     * Computes the sum of the values in `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.4.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * _.sum([4, 2, 8, 6]);
+     * // => 20
+     */
+    function sum(array) {
+      return (array && array.length)
+        ? baseSum(array, identity)
+        : 0;
+    }
+
+    /**
+     * This method is like `_.sum` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the value to be summed.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+     *
+     * _.sumBy(objects, function(o) { return o.n; });
+     * // => 20
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sumBy(objects, 'n');
+     * // => 20
+     */
+    function sumBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSum(array, getIteratee(iteratee))
+        : 0;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // Add methods that return wrapped values in chain sequences.
+    lodash.after = after;
+    lodash.ary = ary;
+    lodash.assign = assign;
+    lodash.assignIn = assignIn;
+    lodash.assignInWith = assignInWith;
+    lodash.assignWith = assignWith;
+    lodash.at = at;
+    lodash.before = before;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.castArray = castArray;
+    lodash.chain = chain;
+    lodash.chunk = chunk;
+    lodash.compact = compact;
+    lodash.concat = concat;
+    lodash.cond = cond;
+    lodash.conforms = conforms;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.curry = curry;
+    lodash.curryRight = curryRight;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defaultsDeep = defaultsDeep;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.differenceBy = differenceBy;
+    lodash.differenceWith = differenceWith;
+    lodash.drop = drop;
+    lodash.dropRight = dropRight;
+    lodash.dropRightWhile = dropRightWhile;
+    lodash.dropWhile = dropWhile;
+    lodash.fill = fill;
+    lodash.filter = filter;
+    lodash.flatMap = flatMap;
+    lodash.flatMapDeep = flatMapDeep;
+    lodash.flatMapDepth = flatMapDepth;
+    lodash.flatten = flatten;
+    lodash.flattenDeep = flattenDeep;
+    lodash.flattenDepth = flattenDepth;
+    lodash.flip = flip;
+    lodash.flow = flow;
+    lodash.flowRight = flowRight;
+    lodash.fromPairs = fromPairs;
+    lodash.functions = functions;
+    lodash.functionsIn = functionsIn;
+    lodash.groupBy = groupBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.intersectionBy = intersectionBy;
+    lodash.intersectionWith = intersectionWith;
+    lodash.invert = invert;
+    lodash.invertBy = invertBy;
+    lodash.invokeMap = invokeMap;
+    lodash.iteratee = iteratee;
+    lodash.keyBy = keyBy;
+    lodash.keys = keys;
+    lodash.keysIn = keysIn;
+    lodash.map = map;
+    lodash.mapKeys = mapKeys;
+    lodash.mapValues = mapValues;
+    lodash.matches = matches;
+    lodash.matchesProperty = matchesProperty;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.mergeWith = mergeWith;
+    lodash.method = method;
+    lodash.methodOf = methodOf;
+    lodash.mixin = mixin;
+    lodash.negate = negate;
+    lodash.nthArg = nthArg;
+    lodash.omit = omit;
+    lodash.omitBy = omitBy;
+    lodash.once = once;
+    lodash.orderBy = orderBy;
+    lodash.over = over;
+    lodash.overArgs = overArgs;
+    lodash.overEvery = overEvery;
+    lodash.overSome = overSome;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.partition = partition;
+    lodash.pick = pick;
+    lodash.pickBy = pickBy;
+    lodash.property = property;
+    lodash.propertyOf = propertyOf;
+    lodash.pull = pull;
+    lodash.pullAll = pullAll;
+    lodash.pullAllBy = pullAllBy;
+    lodash.pullAllWith = pullAllWith;
+    lodash.pullAt = pullAt;
+    lodash.range = range;
+    lodash.rangeRight = rangeRight;
+    lodash.rearg = rearg;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.reverse = reverse;
+    lodash.sampleSize = sampleSize;
+    lodash.set = set;
+    lodash.setWith = setWith;
+    lodash.shuffle = shuffle;
+    lodash.slice = slice;
+    lodash.sortBy = sortBy;
+    lodash.sortedUniq = sortedUniq;
+    lodash.sortedUniqBy = sortedUniqBy;
+    lodash.split = split;
+    lodash.spread = spread;
+    lodash.tail = tail;
+    lodash.take = take;
+    lodash.takeRight = takeRight;
+    lodash.takeRightWhile = takeRightWhile;
+    lodash.takeWhile = takeWhile;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.thru = thru;
+    lodash.toArray = toArray;
+    lodash.toPairs = toPairs;
+    lodash.toPairsIn = toPairsIn;
+    lodash.toPath = toPath;
+    lodash.toPlainObject = toPlainObject;
+    lodash.transform = transform;
+    lodash.unary = unary;
+    lodash.union = union;
+    lodash.unionBy = unionBy;
+    lodash.unionWith = unionWith;
+    lodash.uniq = uniq;
+    lodash.uniqBy = uniqBy;
+    lodash.uniqWith = uniqWith;
+    lodash.unset = unset;
+    lodash.unzip = unzip;
+    lodash.unzipWith = unzipWith;
+    lodash.update = update;
+    lodash.updateWith = updateWith;
+    lodash.values = values;
+    lodash.valuesIn = valuesIn;
+    lodash.without = without;
+    lodash.words = words;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.xorBy = xorBy;
+    lodash.xorWith = xorWith;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+    lodash.zipObjectDeep = zipObjectDeep;
+    lodash.zipWith = zipWith;
+
+    // Add aliases.
+    lodash.entries = toPairs;
+    lodash.entriesIn = toPairsIn;
+    lodash.extend = assignIn;
+    lodash.extendWith = assignInWith;
+
+    // Add methods to `lodash.prototype`.
+    mixin(lodash, lodash);
+
+    /*------------------------------------------------------------------------*/
+
+    // Add methods that return unwrapped values in chain sequences.
+    lodash.add = add;
+    lodash.attempt = attempt;
+    lodash.camelCase = camelCase;
+    lodash.capitalize = capitalize;
+    lodash.ceil = ceil;
+    lodash.clamp = clamp;
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.cloneDeepWith = cloneDeepWith;
+    lodash.cloneWith = cloneWith;
+    lodash.deburr = deburr;
+    lodash.divide = divide;
+    lodash.endsWith = endsWith;
+    lodash.eq = eq;
+    lodash.escape = escape;
+    lodash.escapeRegExp = escapeRegExp;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.floor = floor;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.get = get;
+    lodash.gt = gt;
+    lodash.gte = gte;
+    lodash.has = has;
+    lodash.hasIn = hasIn;
+    lodash.head = head;
+    lodash.identity = identity;
+    lodash.includes = includes;
+    lodash.indexOf = indexOf;
+    lodash.inRange = inRange;
+    lodash.invoke = invoke;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isArrayBuffer = isArrayBuffer;
+    lodash.isArrayLike = isArrayLike;
+    lodash.isArrayLikeObject = isArrayLikeObject;
+    lodash.isBoolean = isBoolean;
+    lodash.isBuffer = isBuffer;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isEqualWith = isEqualWith;
+    lodash.isError = isError;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isInteger = isInteger;
+    lodash.isLength = isLength;
+    lodash.isMap = isMap;
+    lodash.isMatch = isMatch;
+    lodash.isMatchWith = isMatchWith;
+    lodash.isNaN = isNaN;
+    lodash.isNative = isNative;
+    lodash.isNil = isNil;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isObjectLike = isObjectLike;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isSafeInteger = isSafeInteger;
+    lodash.isSet = isSet;
+    lodash.isString = isString;
+    lodash.isSymbol = isSymbol;
+    lodash.isTypedArray = isTypedArray;
+    lodash.isUndefined = isUndefined;
+    lodash.isWeakMap = isWeakMap;
+    lodash.isWeakSet = isWeakSet;
+    lodash.join = join;
+    lodash.kebabCase = kebabCase;
+    lodash.last = last;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.lowerCase = lowerCase;
+    lodash.lowerFirst = lowerFirst;
+    lodash.lt = lt;
+    lodash.lte = lte;
+    lodash.max = max;
+    lodash.maxBy = maxBy;
+    lodash.mean = mean;
+    lodash.meanBy = meanBy;
+    lodash.min = min;
+    lodash.minBy = minBy;
+    lodash.multiply = multiply;
+    lodash.nth = nth;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.pad = pad;
+    lodash.padEnd = padEnd;
+    lodash.padStart = padStart;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.repeat = repeat;
+    lodash.replace = replace;
+    lodash.result = result;
+    lodash.round = round;
+    lodash.runInContext = runInContext;
+    lodash.sample = sample;
+    lodash.size = size;
+    lodash.snakeCase = snakeCase;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.sortedIndexBy = sortedIndexBy;
+    lodash.sortedIndexOf = sortedIndexOf;
+    lodash.sortedLastIndex = sortedLastIndex;
+    lodash.sortedLastIndexBy = sortedLastIndexBy;
+    lodash.sortedLastIndexOf = sortedLastIndexOf;
+    lodash.startCase = startCase;
+    lodash.startsWith = startsWith;
+    lodash.subtract = subtract;
+    lodash.sum = sum;
+    lodash.sumBy = sumBy;
+    lodash.template = template;
+    lodash.times = times;
+    lodash.toInteger = toInteger;
+    lodash.toLength = toLength;
+    lodash.toLower = toLower;
+    lodash.toNumber = toNumber;
+    lodash.toSafeInteger = toSafeInteger;
+    lodash.toString = toString;
+    lodash.toUpper = toUpper;
+    lodash.trim = trim;
+    lodash.trimEnd = trimEnd;
+    lodash.trimStart = trimStart;
+    lodash.truncate = truncate;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+    lodash.upperCase = upperCase;
+    lodash.upperFirst = upperFirst;
+
+    // Add aliases.
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.first = head;
+
+    mixin(lodash, (function() {
+      var source = {};
+      baseForOwn(lodash, function(func, methodName) {
+        if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }()), { 'chain': false });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type {string}
+     */
+    lodash.VERSION = VERSION;
+
+    // Assign default placeholders.
+    arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
+      lodash[methodName].placeholder = lodash;
+    });
+
+    // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+    arrayEach(['drop', 'take'], function(methodName, index) {
+      LazyWrapper.prototype[methodName] = function(n) {
+        var filtered = this.__filtered__;
+        if (filtered && !index) {
+          return new LazyWrapper(this);
+        }
+        n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
+
+        var result = this.clone();
+        if (filtered) {
+          result.__takeCount__ = nativeMin(n, result.__takeCount__);
+        } else {
+          result.__views__.push({
+            'size': nativeMin(n, MAX_ARRAY_LENGTH),
+            'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
+          });
+        }
+        return result;
+      };
+
+      LazyWrapper.prototype[methodName + 'Right'] = function(n) {
+        return this.reverse()[methodName](n).reverse();
+      };
+    });
+
+    // Add `LazyWrapper` methods that accept an `iteratee` value.
+    arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
+      var type = index + 1,
+          isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
+
+      LazyWrapper.prototype[methodName] = function(iteratee) {
+        var result = this.clone();
+        result.__iteratees__.push({
+          'iteratee': getIteratee(iteratee, 3),
+          'type': type
+        });
+        result.__filtered__ = result.__filtered__ || isFilter;
+        return result;
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.head` and `_.last`.
+    arrayEach(['head', 'last'], function(methodName, index) {
+      var takeName = 'take' + (index ? 'Right' : '');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this[takeName](1).value()[0];
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
+    arrayEach(['initial', 'tail'], function(methodName, index) {
+      var dropName = 'drop' + (index ? '' : 'Right');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+      };
+    });
+
+    LazyWrapper.prototype.compact = function() {
+      return this.filter(identity);
+    };
+
+    LazyWrapper.prototype.find = function(predicate) {
+      return this.filter(predicate).head();
+    };
+
+    LazyWrapper.prototype.findLast = function(predicate) {
+      return this.reverse().find(predicate);
+    };
+
+    LazyWrapper.prototype.invokeMap = rest(function(path, args) {
+      if (typeof path == 'function') {
+        return new LazyWrapper(this);
+      }
+      return this.map(function(value) {
+        return baseInvoke(value, path, args);
+      });
+    });
+
+    LazyWrapper.prototype.reject = function(predicate) {
+      predicate = getIteratee(predicate, 3);
+      return this.filter(function(value) {
+        return !predicate(value);
+      });
+    };
+
+    LazyWrapper.prototype.slice = function(start, end) {
+      start = toInteger(start);
+
+      var result = this;
+      if (result.__filtered__ && (start > 0 || end < 0)) {
+        return new LazyWrapper(result);
+      }
+      if (start < 0) {
+        result = result.takeRight(-start);
+      } else if (start) {
+        result = result.drop(start);
+      }
+      if (end !== undefined) {
+        end = toInteger(end);
+        result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+      }
+      return result;
+    };
+
+    LazyWrapper.prototype.takeRightWhile = function(predicate) {
+      return this.reverse().takeWhile(predicate).reverse();
+    };
+
+    LazyWrapper.prototype.toArray = function() {
+      return this.take(MAX_ARRAY_LENGTH);
+    };
+
+    // Add `LazyWrapper` methods to `lodash.prototype`.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
+          isTaker = /^(?:head|last)$/.test(methodName),
+          lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
+          retUnwrapped = isTaker || /^find/.test(methodName);
+
+      if (!lodashFunc) {
+        return;
+      }
+      lodash.prototype[methodName] = function() {
+        var value = this.__wrapped__,
+            args = isTaker ? [1] : arguments,
+            isLazy = value instanceof LazyWrapper,
+            iteratee = args[0],
+            useLazy = isLazy || isArray(value);
+
+        var interceptor = function(value) {
+          var result = lodashFunc.apply(lodash, arrayPush([value], args));
+          return (isTaker && chainAll) ? result[0] : result;
+        };
+
+        if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+          // Avoid lazy use if the iteratee has a "length" value other than `1`.
+          isLazy = useLazy = false;
+        }
+        var chainAll = this.__chain__,
+            isHybrid = !!this.__actions__.length,
+            isUnwrapped = retUnwrapped && !chainAll,
+            onlyLazy = isLazy && !isHybrid;
+
+        if (!retUnwrapped && useLazy) {
+          value = onlyLazy ? value : new LazyWrapper(this);
+          var result = func.apply(value, args);
+          result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
+          return new LodashWrapper(result, chainAll);
+        }
+        if (isUnwrapped && onlyLazy) {
+          return func.apply(this, args);
+        }
+        result = this.thru(interceptor);
+        return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
+      };
+    });
+
+    // Add `Array` methods to `lodash.prototype`.
+    arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+      var func = arrayProto[methodName],
+          chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+          retUnwrapped = /^(?:pop|shift)$/.test(methodName);
+
+      lodash.prototype[methodName] = function() {
+        var args = arguments;
+        if (retUnwrapped && !this.__chain__) {
+          var value = this.value();
+          return func.apply(isArray(value) ? value : [], args);
+        }
+        return this[chainName](function(value) {
+          return func.apply(isArray(value) ? value : [], args);
+        });
+      };
+    });
+
+    // Map minified method names to their real names.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var lodashFunc = lodash[methodName];
+      if (lodashFunc) {
+        var key = (lodashFunc.name + ''),
+            names = realNames[key] || (realNames[key] = []);
+
+        names.push({ 'name': methodName, 'func': lodashFunc });
+      }
+    });
+
+    realNames[createHybridWrapper(undefined, BIND_KEY_FLAG).name] = [{
+      'name': 'wrapper',
+      'func': undefined
+    }];
+
+    // Add methods to `LazyWrapper`.
+    LazyWrapper.prototype.clone = lazyClone;
+    LazyWrapper.prototype.reverse = lazyReverse;
+    LazyWrapper.prototype.value = lazyValue;
+
+    // Add chain sequence methods to the `lodash` wrapper.
+    lodash.prototype.at = wrapperAt;
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.commit = wrapperCommit;
+    lodash.prototype.next = wrapperNext;
+    lodash.prototype.plant = wrapperPlant;
+    lodash.prototype.reverse = wrapperReverse;
+    lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+    if (iteratorSymbol) {
+      lodash.prototype[iteratorSymbol] = wrapperToIterator;
+    }
+    return lodash;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Export lodash.
+  var _ = runInContext();
+
+  // Expose Lodash on the free variable `window` or `self` when available so it's
+  // globally accessible, even when bundled with Browserify, Webpack, etc. This
+  // also prevents errors in cases where Lodash is loaded by a script tag in the
+  // presence of an AMD loader. See http://requirejs.org/docs/errors.html#mismatch
+  // for more details. Use `_.noConflict` to remove Lodash from the global object.
+  (freeWindow || freeSelf || {})._ = _;
+
+  // Some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module.
+    define(function() {
+      return _;
+    });
+  }
+  // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
+  else if (freeExports && freeModule) {
+    // Export for Node.js.
+    if (moduleExports) {
+      (freeModule.exports = _)._ = _;
+    }
+    // Export for CommonJS support.
+    freeExports._ = _;
+  }
+  else {
+    // Export to the global object.
+    root._ = _;
+  }
+}.call(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.min.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.min.js
new file mode 100644
index 0000000..4994229
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/lodash.min.js
@@ -0,0 +1,126 @@
+/**
+ * @license
+ * lodash 4.11.2 (Custom Build) lodash.com/license | Underscore.js 1.8.3 underscorejs.org/LICENSE
+ * Build: `lodash -o ./dist/lodash.js`
+ */
+;(function(){function t(t,n){return t.set(n[0],n[1]),t}function n(t,n){return t.add(n),t}function r(t,n,r){switch(r.length){case 0:return t.call(n);case 1:return t.call(n,r[0]);case 2:return t.call(n,r[0],r[1]);case 3:return t.call(n,r[0],r[1],r[2])}return t.apply(n,r)}function e(t,n,r,e){for(var u=-1,o=t.length;++u<o;){var i=t[u];n(e,i,r(i),t)}return e}function u(t,n){for(var r=-1,e=t.length;++r<e&&false!==n(t[r],r,t););return t}function o(t,n){for(var r=-1,e=t.length;++r<e;)if(!n(t[r],r,t))return false;
+return true}function i(t,n){for(var r=-1,e=t.length,u=0,o=[];++r<e;){var i=t[r];n(i,r,t)&&(o[u++]=i)}return o}function f(t,n){return!!t.length&&-1<g(t,n,0)}function c(t,n,r){for(var e=-1,u=t.length;++e<u;)if(r(n,t[e]))return true;return false}function a(t,n){for(var r=-1,e=t.length,u=Array(e);++r<e;)u[r]=n(t[r],r,t);return u}function l(t,n){for(var r=-1,e=n.length,u=t.length;++r<e;)t[u+r]=n[r];return t}function s(t,n,r,e){var u=-1,o=t.length;for(e&&o&&(r=t[++u]);++u<o;)r=n(r,t[u],u,t);return r}function h(t,n,r,e){
+var u=t.length;for(e&&u&&(r=t[--u]);u--;)r=n(r,t[u],u,t);return r}function p(t,n){for(var r=-1,e=t.length;++r<e;)if(n(t[r],r,t))return true;return false}function _(t,n,r,e){var u;return r(t,function(t,r,o){return n(t,r,o)?(u=e?r:t,false):void 0}),u}function v(t,n,r){for(var e=t.length,u=r?e:-1;r?u--:++u<e;)if(n(t[u],u,t))return u;return-1}function g(t,n,r){if(n!==n)return B(t,r);--r;for(var e=t.length;++r<e;)if(t[r]===n)return r;return-1}function d(t,n,r,e){--r;for(var u=t.length;++r<u;)if(e(t[r],n))return r;
+return-1}function y(t,n){var r=t?t.length:0;return r?j(t,n)/r:Z}function b(t,n,r,e,u){return u(t,function(t,u,o){r=e?(e=false,t):n(r,t,u,o)}),r}function x(t,n){var r=t.length;for(t.sort(n);r--;)t[r]=t[r].c;return t}function j(t,n){for(var r,e=-1,u=t.length;++e<u;){var o=n(t[e]);o!==N&&(r=r===N?o:r+o)}return r}function m(t,n){for(var r=-1,e=Array(t);++r<t;)e[r]=n(r);return e}function w(t,n){return a(n,function(n){return[n,t[n]]})}function A(t){return function(n){return t(n)}}function O(t,n){return a(n,function(n){
+return t[n]})}function k(t,n){for(var r=-1,e=t.length;++r<e&&-1<g(n,t[r],0););return r}function E(t,n){for(var r=t.length;r--&&-1<g(n,t[r],0););return r}function I(t){return t&&t.Object===Object?t:null}function S(t){return Lt[t]}function R(t){return Ct[t]}function W(t){return"\\"+zt[t]}function B(t,n,r){var e=t.length;for(n+=r?0:-1;r?n--:++n<e;){var u=t[n];if(u!==u)return n}return-1}function L(t){var n=false;if(null!=t&&typeof t.toString!="function")try{n=!!(t+"")}catch(r){}return n}function C(t){for(var n,r=[];!(n=t.next()).done;)r.push(n.value);
+return r}function M(t){var n=-1,r=Array(t.size);return t.forEach(function(t,e){r[++n]=[e,t]}),r}function U(t,n){for(var r=-1,e=t.length,u=0,o=[];++r<e;){var i=t[r];i!==n&&"__lodash_placeholder__"!==i||(t[r]="__lodash_placeholder__",o[u++]=r)}return o}function z(t){var n=-1,r=Array(t.size);return t.forEach(function(t){r[++n]=t}),r}function D(t){if(!t||!It.test(t))return t.length;for(var n=kt.lastIndex=0;kt.test(t);)n++;return n}function $(t){return Mt[t]}function F(I){function jt(t){if(De(t)&&!li(t)&&!(t instanceof Lt)){
+if(t instanceof wt)return t;if(wu.call(t,"__wrapped__"))return oe(t)}return new wt(t)}function mt(){}function wt(t,n){this.__wrapped__=t,this.__actions__=[],this.__chain__=!!n,this.__index__=0,this.__values__=N}function Lt(t){this.__wrapped__=t,this.__actions__=[],this.__dir__=1,this.__filtered__=false,this.__iteratees__=[],this.__takeCount__=4294967295,this.__views__=[]}function Ct(){}function Mt(t){var n=-1,r=t?t.length:0;for(this.clear();++n<r;){var e=t[n];this.set(e[0],e[1])}}function Ut(t){var n=-1,r=t?t.length:0;
+for(this.__data__=new Mt;++n<r;)this.push(t[n])}function zt(t,n){var r=t.__data__;return Hr(n)?(r=r.__data__,"__lodash_hash_undefined__"===(typeof n=="string"?r.string:r.hash)[n]):r.has(n)}function Ft(t){var n=-1,r=t?t.length:0;for(this.clear();++n<r;){var e=t[n];this.set(e[0],e[1])}}function Nt(t,n){var r=Tt(t,n);return 0>r?false:(r==t.length-1?t.pop():Fu.call(t,r,1),true)}function Zt(t,n){var r=Tt(t,n);return 0>r?N:t[r][1]}function Tt(t,n){for(var r=t.length;r--;)if(Se(t[r][0],n))return r;return-1}function qt(t,n,r){
+var e=Tt(t,n);0>e?t.push([n,r]):t[e][1]=r}function Gt(t,n,r,e){return t===N||Se(t,xu[r])&&!wu.call(e,r)?n:t}function Jt(t,n,r){(r===N||Se(t[n],r))&&(typeof n!="number"||r!==N||n in t)||(t[n]=r)}function Yt(t,n,r){var e=t[n];wu.call(t,n)&&Se(e,r)&&(r!==N||n in t)||(t[n]=r)}function Ht(t,n,r,e){return yo(t,function(t,u,o){n(e,t,r(t),o)}),e}function Qt(t,n){return t&&ar(n,tu(n),t)}function Xt(t,n){for(var r=-1,e=null==t,u=n.length,o=Array(u);++r<u;)o[r]=e?N:Qe(t,n[r]);return o}function tn(t,n,r){return t===t&&(r!==N&&(t=r>=t?t:r),
+n!==N&&(t=t>=n?t:n)),t}function nn(t,n,r,e,o,i,f){var c;if(e&&(c=i?e(t,o,i,f):e(t)),c!==N)return c;if(!ze(t))return t;if(o=li(t)){if(c=Pr(t),!n)return cr(t,c)}else{var a=Fr(t),l="[object Function]"==a||"[object GeneratorFunction]"==a;if(si(t))return er(t,n);if("[object Object]"==a||"[object Arguments]"==a||l&&!i){if(L(t))return i?t:{};if(c=Zr(l?{}:t),!n)return lr(t,Qt(c,t))}else{if(!Bt[a])return i?t:{};c=Tr(t,a,nn,n)}}if(f||(f=new Ft),i=f.get(t))return i;if(f.set(t,c),!o)var s=r?vn(t,tu,$r):tu(t);
+return u(s||t,function(u,o){s&&(o=u,u=t[o]),Yt(c,o,nn(u,n,r,e,o,t,f))}),c}function rn(t){var n=tu(t),r=n.length;return function(e){if(null==e)return!r;for(var u=r;u--;){var o=n[u],i=t[o],f=e[o];if(f===N&&!(o in Object(e))||!i(f))return false}return true}}function en(t){return ze(t)?zu(t):{}}function un(t,n,r){if(typeof t!="function")throw new yu("Expected a function");return $u(function(){t.apply(N,r)},n)}function on(t,n,r,e){var u=-1,o=f,i=true,l=t.length,s=[],h=n.length;if(!l)return s;r&&(n=a(n,A(r))),e?(o=c,
+i=false):n.length>=200&&(o=zt,i=false,n=new Ut(n));t:for(;++u<l;){var p=t[u],_=r?r(p):p,p=e||0!==p?p:0;if(i&&_===_){for(var v=h;v--;)if(n[v]===_)continue t;s.push(p)}else o(n,_,e)||s.push(p)}return s}function fn(t,n){var r=true;return yo(t,function(t,e,u){return r=!!n(t,e,u)}),r}function cn(t,n,r){for(var e=-1,u=t.length;++e<u;){var o=t[e],i=n(o);if(null!=i&&(f===N?i===i&&!Te(i):r(i,f)))var f=i,c=o}return c}function an(t,n){var r=[];return yo(t,function(t,e,u){n(t,e,u)&&r.push(t)}),r}function ln(t,n,r,e,u){
+var o=-1,i=t.length;for(r||(r=Vr),u||(u=[]);++o<i;){var f=t[o];n>0&&r(f)?n>1?ln(f,n-1,r,e,u):l(u,f):e||(u[u.length]=f)}return u}function sn(t,n){return t&&xo(t,n,tu)}function hn(t,n){return t&&jo(t,n,tu)}function pn(t,n){return i(n,function(n){return Ce(t[n])})}function _n(t,n){n=Yr(n,t)?[n]:nr(n);for(var r=0,e=n.length;null!=t&&e>r;)t=t[ee(n[r++])];return r&&r==e?t:N}function vn(t,n,r){return n=n(t),li(t)?n:l(n,r(t))}function gn(t,n){return t>n}function dn(t,n){return wu.call(t,n)||typeof t=="object"&&n in t&&null===Zu(Object(t));
+}function yn(t,n){return n in Object(t)}function bn(t,n,r){for(var e=r?c:f,u=t[0].length,o=t.length,i=o,l=Array(o),s=1/0,h=[];i--;){var p=t[i];i&&n&&(p=a(p,A(n))),s=Gu(p.length,s),l[i]=!r&&(n||u>=120&&p.length>=120)?new Ut(i&&p):N}var p=t[0],_=-1,v=l[0];t:for(;++_<u&&s>h.length;){var g=p[_],d=n?n(g):g,g=r||0!==g?g:0;if(v?!zt(v,d):!e(h,d,r)){for(i=o;--i;){var y=l[i];if(y?!zt(y,d):!e(t[i],d,r))continue t}v&&v.push(d),h.push(g)}}return h}function xn(t,n,r){var e={};return sn(t,function(t,u,o){n(e,r(t),u,o);
+}),e}function jn(t,n,e){return Yr(n,t)||(n=nr(n),t=re(t,n),n=ae(n)),n=null==t?t:t[ee(n)],null==n?N:r(n,t,e)}function mn(t,n,r,e,u){if(t===n)n=true;else if(null==t||null==n||!ze(t)&&!De(n))n=t!==t&&n!==n;else t:{var o=li(t),i=li(n),f="[object Array]",c="[object Array]";o||(f=Fr(t),f="[object Arguments]"==f?"[object Object]":f),i||(c=Fr(n),c="[object Arguments]"==c?"[object Object]":c);var a="[object Object]"==f&&!L(t),i="[object Object]"==c&&!L(n);if((c=f==c)&&!a)u||(u=new Ft),n=o||qe(t)?Br(t,n,mn,r,e,u):Lr(t,n,f,mn,r,e,u);else{
+if(!(2&e)&&(o=a&&wu.call(t,"__wrapped__"),f=i&&wu.call(n,"__wrapped__"),o||f)){t=o?t.value():t,n=f?n.value():n,u||(u=new Ft),n=mn(t,n,r,e,u);break t}if(c)n:if(u||(u=new Ft),o=2&e,f=tu(t),i=f.length,c=tu(n).length,i==c||o){for(a=i;a--;){var l=f[a];if(!(o?l in n:dn(n,l))){n=false;break n}}if(c=u.get(t))n=c==n;else{c=true,u.set(t,n);for(var s=o;++a<i;){var l=f[a],h=t[l],p=n[l];if(r)var _=o?r(p,h,l,n,t,u):r(h,p,l,t,n,u);if(_===N?h!==p&&!mn(h,p,r,e,u):!_){c=false;break}s||(s="constructor"==l)}c&&!s&&(r=t.constructor,
+e=n.constructor,r!=e&&"constructor"in t&&"constructor"in n&&!(typeof r=="function"&&r instanceof r&&typeof e=="function"&&e instanceof e)&&(c=false)),u["delete"](t),n=c}}else n=false;else n=false}}return n}function wn(t,n,r,e){var u=r.length,o=u,i=!e;if(null==t)return!o;for(t=Object(t);u--;){var f=r[u];if(i&&f[2]?f[1]!==t[f[0]]:!(f[0]in t))return false}for(;++u<o;){var f=r[u],c=f[0],a=t[c],l=f[1];if(i&&f[2]){if(a===N&&!(c in t))return false}else{if(f=new Ft,e)var s=e(a,l,c,t,n,f);if(s===N?!mn(l,a,e,3,f):!s)return false;
+}}return true}function An(t){return typeof t=="function"?t:null==t?au:typeof t=="object"?li(t)?Sn(t[0],t[1]):In(t):pu(t)}function On(t){t=null==t?t:Object(t);var n,r=[];for(n in t)r.push(n);return r}function kn(t,n){return n>t}function En(t,n){var r=-1,e=We(t)?Array(t.length):[];return yo(t,function(t,u,o){e[++r]=n(t,u,o)}),e}function In(t){var n=Ur(t);return 1==n.length&&n[0][2]?te(n[0][0],n[0][1]):function(r){return r===t||wn(r,t,n)}}function Sn(t,n){return Yr(t)&&n===n&&!ze(n)?te(ee(t),n):function(r){
+var e=Qe(r,t);return e===N&&e===n?Xe(r,t):mn(n,e,N,3)}}function Rn(t,n,r,e,o){if(t!==n){if(!li(n)&&!qe(n))var i=nu(n);u(i||n,function(u,f){if(i&&(f=u,u=n[f]),ze(u)){o||(o=new Ft);var c=f,a=o,l=t[c],s=n[c],h=a.get(s);if(h)Jt(t,c,h);else{var h=e?e(l,s,c+"",t,n,a):N,p=h===N;p&&(h=s,li(s)||qe(s)?li(l)?h=l:Be(l)?h=cr(l):(p=false,h=nn(s,true)):Ne(s)||Re(s)?Re(l)?h=Ye(l):!ze(l)||r&&Ce(l)?(p=false,h=nn(s,true)):h=l:p=false),a.set(s,h),p&&Rn(h,s,r,e,a),a["delete"](s),Jt(t,c,h)}}else c=e?e(t[f],u,f+"",t,n,o):N,c===N&&(c=u),
+Jt(t,f,c)})}}function Wn(t,n){var r=t.length;return r?(n+=0>n?r:0,Gr(n,r)?t[n]:N):void 0}function Bn(t,n,r){var e=-1;return n=a(n.length?n:[au],A(Mr())),t=En(t,function(t){return{a:a(n,function(n){return n(t)}),b:++e,c:t}}),x(t,function(t,n){var e;t:{e=-1;for(var u=t.a,o=n.a,i=u.length,f=r.length;++e<i;){var c=or(u[e],o[e]);if(c){e=e>=f?c:c*("desc"==r[e]?-1:1);break t}}e=t.b-n.b}return e})}function Ln(t,n){return t=Object(t),s(n,function(n,r){return r in t&&(n[r]=t[r]),n},{})}function Cn(t,n){for(var r=-1,e=vn(t,nu,ko),u=e.length,o={};++r<u;){
+var i=e[r],f=t[i];n(f,i)&&(o[i]=f)}return o}function Mn(t){return function(n){return null==n?N:n[t]}}function Un(t){return function(n){return _n(n,t)}}function zn(t,n,r,e){var u=e?d:g,o=-1,i=n.length,f=t;for(r&&(f=a(t,A(r)));++o<i;)for(var c=0,l=n[o],l=r?r(l):l;-1<(c=u(f,l,c,e));)f!==t&&Fu.call(f,c,1),Fu.call(t,c,1);return t}function Dn(t,n){for(var r=t?n.length:0,e=r-1;r--;){var u=n[r];if(r==e||u!==o){var o=u;if(Gr(u))Fu.call(t,u,1);else if(Yr(u,t))delete t[ee(u)];else{var u=nr(u),i=re(t,u);null!=i&&delete i[ee(ae(u))];
+}}}}function $n(t,n){return t+Pu(Yu()*(n-t+1))}function Fn(t,n){var r="";if(!t||1>n||n>9007199254740991)return r;do n%2&&(r+=t),(n=Pu(n/2))&&(t+=t);while(n);return r}function Nn(t,n,r,e){n=Yr(n,t)?[n]:nr(n);for(var u=-1,o=n.length,i=o-1,f=t;null!=f&&++u<o;){var c=ee(n[u]);if(ze(f)){var a=r;if(u!=i){var l=f[c],a=e?e(l,c,f):N;a===N&&(a=null==l?Gr(n[u+1])?[]:{}:l)}Yt(f,c,a)}f=f[c]}return t}function Pn(t,n,r){var e=-1,u=t.length;for(0>n&&(n=-n>u?0:u+n),r=r>u?u:r,0>r&&(r+=u),u=n>r?0:r-n>>>0,n>>>=0,r=Array(u);++e<u;)r[e]=t[e+n];
+return r}function Zn(t,n){var r;return yo(t,function(t,e,u){return r=n(t,e,u),!r}),!!r}function Tn(t,n,r){var e=0,u=t?t.length:e;if(typeof n=="number"&&n===n&&2147483647>=u){for(;u>e;){var o=e+u>>>1,i=t[o];null!==i&&!Te(i)&&(r?n>=i:n>i)?e=o+1:u=o}return u}return qn(t,n,au,r)}function qn(t,n,r,e){n=r(n);for(var u=0,o=t?t.length:0,i=n!==n,f=null===n,c=Te(n),a=n===N;o>u;){var l=Pu((u+o)/2),s=r(t[l]),h=s!==N,p=null===s,_=s===s,v=Te(s);(i?e||_:a?_&&(e||h):f?_&&h&&(e||!p):c?_&&h&&!p&&(e||!v):p||v?0:e?n>=s:n>s)?u=l+1:o=l;
+}return Gu(o,4294967294)}function Vn(t,n){for(var r=-1,e=t.length,u=0,o=[];++r<e;){var i=t[r],f=n?n(i):i;if(!r||!Se(f,c)){var c=f;o[u++]=0===i?0:i}}return o}function Kn(t){return typeof t=="number"?t:Te(t)?Z:+t}function Gn(t){if(typeof t=="string")return t;if(Te(t))return go?go.call(t):"";var n=t+"";return"0"==n&&1/t==-P?"-0":n}function Jn(t,n,r){var e=-1,u=f,o=t.length,i=true,a=[],l=a;if(r)i=false,u=c;else if(o>=200){if(u=n?null:wo(t))return z(u);i=false,u=zt,l=new Ut}else l=n?[]:a;t:for(;++e<o;){var s=t[e],h=n?n(s):s,s=r||0!==s?s:0;
+if(i&&h===h){for(var p=l.length;p--;)if(l[p]===h)continue t;n&&l.push(h),a.push(s)}else u(l,h,r)||(l!==a&&l.push(h),a.push(s))}return a}function Yn(t,n,r,e){for(var u=t.length,o=e?u:-1;(e?o--:++o<u)&&n(t[o],o,t););return r?Pn(t,e?0:o,e?o+1:u):Pn(t,e?o+1:0,e?u:o)}function Hn(t,n){var r=t;return r instanceof Lt&&(r=r.value()),s(n,function(t,n){return n.func.apply(n.thisArg,l([t],n.args))},r)}function Qn(t,n,r){for(var e=-1,u=t.length;++e<u;)var o=o?l(on(o,t[e],n,r),on(t[e],o,n,r)):t[e];return o&&o.length?Jn(o,n,r):[];
+}function Xn(t,n,r){for(var e=-1,u=t.length,o=n.length,i={};++e<u;)r(i,t[e],o>e?n[e]:N);return i}function tr(t){return Be(t)?t:[]}function nr(t){return li(t)?t:Io(t)}function rr(t,n,r){var e=t.length;return r=r===N?e:r,!n&&r>=e?t:Pn(t,n,r)}function er(t,n){if(n)return t.slice();var r=new t.constructor(t.length);return t.copy(r),r}function ur(t){var n=new t.constructor(t.byteLength);return new Bu(n).set(new Bu(t)),n}function or(t,n){if(t!==n){var r=t!==N,e=null===t,u=t===t,o=Te(t),i=n!==N,f=null===n,c=n===n,a=Te(n);
+if(!f&&!a&&!o&&t>n||o&&i&&c&&!f&&!a||e&&i&&c||!r&&c||!u)return 1;if(!e&&!o&&!a&&n>t||a&&r&&u&&!e&&!o||f&&r&&u||!i&&u||!c)return-1}return 0}function ir(t,n,r,e){var u=-1,o=t.length,i=r.length,f=-1,c=n.length,a=Ku(o-i,0),l=Array(c+a);for(e=!e;++f<c;)l[f]=n[f];for(;++u<i;)(e||o>u)&&(l[r[u]]=t[u]);for(;a--;)l[f++]=t[u++];return l}function fr(t,n,r,e){var u=-1,o=t.length,i=-1,f=r.length,c=-1,a=n.length,l=Ku(o-f,0),s=Array(l+a);for(e=!e;++u<l;)s[u]=t[u];for(l=u;++c<a;)s[l+c]=n[c];for(;++i<f;)(e||o>u)&&(s[l+r[i]]=t[u++]);
+return s}function cr(t,n){var r=-1,e=t.length;for(n||(n=Array(e));++r<e;)n[r]=t[r];return n}function ar(t,n,r,e){r||(r={});for(var u=-1,o=n.length;++u<o;){var i=n[u],f=e?e(r[i],t[i],i,r,t):t[i];Yt(r,i,f)}return r}function lr(t,n){return ar(t,$r(t),n)}function sr(t,n){return function(r,u){var o=li(r)?e:Ht,i=n?n():{};return o(r,t,Mr(u),i)}}function hr(t){return Ee(function(n,r){var e=-1,u=r.length,o=u>1?r[u-1]:N,i=u>2?r[2]:N,o=typeof o=="function"?(u--,o):N;for(i&&Jr(r[0],r[1],i)&&(o=3>u?N:o,u=1),n=Object(n);++e<u;)(i=r[e])&&t(n,i,e,o);
+return n})}function pr(t,n){return function(r,e){if(null==r)return r;if(!We(r))return t(r,e);for(var u=r.length,o=n?u:-1,i=Object(r);(n?o--:++o<u)&&false!==e(i[o],o,i););return r}}function _r(t){return function(n,r,e){var u=-1,o=Object(n);e=e(n);for(var i=e.length;i--;){var f=e[t?i:++u];if(false===r(o[f],f,o))break}return n}}function vr(t,n,r){function e(){return(this&&this!==Vt&&this instanceof e?o:t).apply(u?r:this,arguments)}var u=1&n,o=yr(t);return e}function gr(t){return function(n){n=He(n);var r=It.test(n)?n.match(kt):N,e=r?r[0]:n.charAt(0);
+return n=r?rr(r,1).join(""):n.slice(1),e[t]()+n}}function dr(t){return function(n){return s(fu(iu(n).replace(At,"")),t,"")}}function yr(t){return function(){var n=arguments;switch(n.length){case 0:return new t;case 1:return new t(n[0]);case 2:return new t(n[0],n[1]);case 3:return new t(n[0],n[1],n[2]);case 4:return new t(n[0],n[1],n[2],n[3]);case 5:return new t(n[0],n[1],n[2],n[3],n[4]);case 6:return new t(n[0],n[1],n[2],n[3],n[4],n[5]);case 7:return new t(n[0],n[1],n[2],n[3],n[4],n[5],n[6])}var r=en(t.prototype),n=t.apply(r,n);
+return ze(n)?n:r}}function br(t,n,e){function u(){for(var i=arguments.length,f=Array(i),c=i,a=Dr(u);c--;)f[c]=arguments[c];return c=3>i&&f[0]!==a&&f[i-1]!==a?[]:U(f,a),i-=c.length,e>i?Sr(t,n,jr,u.placeholder,N,f,c,N,N,e-i):r(this&&this!==Vt&&this instanceof u?o:t,this,f)}var o=yr(t);return u}function xr(t){return Ee(function(n){n=ln(n,1);var r=n.length,e=r,u=wt.prototype.thru;for(t&&n.reverse();e--;){var o=n[e];if(typeof o!="function")throw new yu("Expected a function");if(u&&!i&&"wrapper"==Cr(o))var i=new wt([],true);
+}for(e=i?e:r;++e<r;)var o=n[e],u=Cr(o),f="wrapper"==u?Ao(o):N,i=f&&Qr(f[0])&&424==f[1]&&!f[4].length&&1==f[9]?i[Cr(f[0])].apply(i,f[3]):1==o.length&&Qr(o)?i[u]():i.thru(o);return function(){var t=arguments,e=t[0];if(i&&1==t.length&&li(e)&&e.length>=200)return i.plant(e).value();for(var u=0,t=r?n[u].apply(this,t):e;++u<r;)t=n[u].call(this,t);return t}})}function jr(t,n,r,e,u,o,i,f,c,a){function l(){for(var d=arguments.length,y=d,b=Array(d);y--;)b[y]=arguments[y];if(_){var x,j=Dr(l),y=b.length;for(x=0;y--;)b[y]===j&&x++;
+}if(e&&(b=ir(b,e,u,_)),o&&(b=fr(b,o,i,_)),d-=x,_&&a>d)return j=U(b,j),Sr(t,n,jr,l.placeholder,r,b,j,f,c,a-d);if(j=h?r:this,y=p?j[t]:t,d=b.length,f){x=b.length;for(var m=Gu(f.length,x),w=cr(b);m--;){var A=f[m];b[m]=Gr(A,x)?w[A]:N}}else v&&d>1&&b.reverse();return s&&d>c&&(b.length=c),this&&this!==Vt&&this instanceof l&&(y=g||yr(y)),y.apply(j,b)}var s=128&n,h=1&n,p=2&n,_=24&n,v=512&n,g=p?N:yr(t);return l}function mr(t,n){return function(r,e){return xn(r,t,n(e))}}function wr(t){return function(n,r){var e;
+if(n===N&&r===N)return 0;if(n!==N&&(e=n),r!==N){if(e===N)return r;typeof n=="string"||typeof r=="string"?(n=Gn(n),r=Gn(r)):(n=Kn(n),r=Kn(r)),e=t(n,r)}return e}}function Ar(t){return Ee(function(n){return n=1==n.length&&li(n[0])?a(n[0],A(Mr())):a(ln(n,1,Kr),A(Mr())),Ee(function(e){var u=this;return t(n,function(t){return r(t,u,e)})})})}function Or(t,n){n=n===N?" ":Gn(n);var r=n.length;return 2>r?r?Fn(n,t):n:(r=Fn(n,Nu(t/D(n))),It.test(n)?rr(r.match(kt),0,t).join(""):r.slice(0,t))}function kr(t,n,e,u){
+function o(){for(var n=-1,c=arguments.length,a=-1,l=u.length,s=Array(l+c),h=this&&this!==Vt&&this instanceof o?f:t;++a<l;)s[a]=u[a];for(;c--;)s[a++]=arguments[++n];return r(h,i?e:this,s)}var i=1&n,f=yr(t);return o}function Er(t){return function(n,r,e){e&&typeof e!="number"&&Jr(n,r,e)&&(r=e=N),n=Je(n),n=n===n?n:0,r===N?(r=n,n=0):r=Je(r)||0,e=e===N?r>n?1:-1:Je(e)||0;var u=-1;r=Ku(Nu((r-n)/(e||1)),0);for(var o=Array(r);r--;)o[t?r:++u]=n,n+=e;return o}}function Ir(t){return function(n,r){return typeof n=="string"&&typeof r=="string"||(n=Je(n),
+r=Je(r)),t(n,r)}}function Sr(t,n,r,e,u,o,i,f,c,a){var l=8&n,s=l?i:N;i=l?N:i;var h=l?o:N;return o=l?N:o,n=(n|(l?32:64))&~(l?64:32),4&n||(n&=-4),n=[t,n,u,h,s,o,i,f,c,a],r=r.apply(N,n),Qr(t)&&Eo(r,n),r.placeholder=e,r}function Rr(t){var n=gu[t];return function(t,r){if(t=Je(t),r=Ke(r)){var e=(He(t)+"e").split("e"),e=n(e[0]+"e"+(+e[1]+r)),e=(He(e)+"e").split("e");return+(e[0]+"e"+(+e[1]-r))}return n(t)}}function Wr(t,n,r,e,u,o,i,f){var c=2&n;if(!c&&typeof t!="function")throw new yu("Expected a function");
+var a=e?e.length:0;if(a||(n&=-97,e=u=N),i=i===N?i:Ku(Ke(i),0),f=f===N?f:Ke(f),a-=u?u.length:0,64&n){var l=e,s=u;e=u=N}var h=c?N:Ao(t);return o=[t,n,r,e,u,l,s,o,i,f],h&&(r=o[1],t=h[1],n=r|t,e=128==t&&8==r||128==t&&256==r&&h[8]>=o[7].length||384==t&&h[8]>=h[7].length&&8==r,131>n||e)&&(1&t&&(o[2]=h[2],n|=1&r?0:4),(r=h[3])&&(e=o[3],o[3]=e?ir(e,r,h[4]):r,o[4]=e?U(o[3],"__lodash_placeholder__"):h[4]),(r=h[5])&&(e=o[5],o[5]=e?fr(e,r,h[6]):r,o[6]=e?U(o[5],"__lodash_placeholder__"):h[6]),(r=h[7])&&(o[7]=r),
+128&t&&(o[8]=null==o[8]?h[8]:Gu(o[8],h[8])),null==o[9]&&(o[9]=h[9]),o[0]=h[0],o[1]=n),t=o[0],n=o[1],r=o[2],e=o[3],u=o[4],f=o[9]=null==o[9]?c?0:t.length:Ku(o[9]-a,0),!f&&24&n&&(n&=-25),(h?mo:Eo)(n&&1!=n?8==n||16==n?br(t,n,f):32!=n&&33!=n||u.length?jr.apply(N,o):kr(t,n,r,e):vr(t,n,r),o)}function Br(t,n,r,e,u,o){var i=-1,f=2&u,c=1&u,a=t.length,l=n.length;if(a!=l&&!(f&&l>a))return false;if(l=o.get(t))return l==n;for(l=true,o.set(t,n);++i<a;){var s=t[i],h=n[i];if(e)var _=f?e(h,s,i,n,t,o):e(s,h,i,t,n,o);if(_!==N){
+if(_)continue;l=false;break}if(c){if(!p(n,function(t){return s===t||r(s,t,e,u,o)})){l=false;break}}else if(s!==h&&!r(s,h,e,u,o)){l=false;break}}return o["delete"](t),l}function Lr(t,n,r,e,u,o,i){switch(r){case"[object DataView]":if(t.byteLength!=n.byteLength||t.byteOffset!=n.byteOffset)break;t=t.buffer,n=n.buffer;case"[object ArrayBuffer]":if(t.byteLength!=n.byteLength||!e(new Bu(t),new Bu(n)))break;return true;case"[object Boolean]":case"[object Date]":return+t==+n;case"[object Error]":return t.name==n.name&&t.message==n.message;
+case"[object Number]":return t!=+t?n!=+n:t==+n;case"[object RegExp]":case"[object String]":return t==n+"";case"[object Map]":var f=M;case"[object Set]":if(f||(f=z),t.size!=n.size&&!(2&o))break;return(r=i.get(t))?r==n:(o|=1,i.set(t,n),Br(f(t),f(n),e,u,o,i));case"[object Symbol]":if(vo)return vo.call(t)==vo.call(n)}return false}function Cr(t){for(var n=t.name+"",r=co[n],e=wu.call(co,n)?r.length:0;e--;){var u=r[e],o=u.func;if(null==o||o==t)return u.name}return n}function Mr(){var t=jt.iteratee||lu,t=t===lu?An:t;
+return arguments.length?t(arguments[0],arguments[1]):t}function Ur(t){t=ru(t);for(var n=t.length;n--;){var r=t[n][1];t[n][2]=r===r&&!ze(r)}return t}function zr(t,n){var r=t[n];return $e(r)?r:N}function Dr(t){return(wu.call(jt,"placeholder")?jt:t).placeholder}function $r(t){return Mu(Object(t))}function Fr(t){return ku.call(t)}function Nr(t,n,r){n=Yr(n,t)?[n]:nr(n);for(var e,u=-1,o=n.length;++u<o;){var i=ee(n[u]);if(!(e=null!=t&&r(t,i)))break;t=t[i]}return e?e:(o=t?t.length:0,!!o&&Ue(o)&&Gr(i,o)&&(li(t)||Ze(t)||Re(t)));
+}function Pr(t){var n=t.length,r=t.constructor(n);return n&&"string"==typeof t[0]&&wu.call(t,"index")&&(r.index=t.index,r.input=t.input),r}function Zr(t){return typeof t.constructor!="function"||Xr(t)?{}:en(Zu(Object(t)))}function Tr(r,e,u,o){var i=r.constructor;switch(e){case"[object ArrayBuffer]":return ur(r);case"[object Boolean]":case"[object Date]":return new i(+r);case"[object DataView]":return e=o?ur(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.byteLength);case"[object Float32Array]":
+case"[object Float64Array]":case"[object Int8Array]":case"[object Int16Array]":case"[object Int32Array]":case"[object Uint8Array]":case"[object Uint8ClampedArray]":case"[object Uint16Array]":case"[object Uint32Array]":return e=o?ur(r.buffer):r.buffer,new r.constructor(e,r.byteOffset,r.length);case"[object Map]":return e=o?u(M(r),true):M(r),s(e,t,new r.constructor);case"[object Number]":case"[object String]":return new i(r);case"[object RegExp]":return e=new r.constructor(r.source,st.exec(r)),e.lastIndex=r.lastIndex,
+e;case"[object Set]":return e=o?u(z(r),true):z(r),s(e,n,new r.constructor);case"[object Symbol]":return vo?Object(vo.call(r)):{}}}function qr(t){var n=t?t.length:N;return Ue(n)&&(li(t)||Ze(t)||Re(t))?m(n,String):null}function Vr(t){return Be(t)&&(li(t)||Re(t))}function Kr(t){return li(t)&&!(2==t.length&&!Ce(t[0]))}function Gr(t,n){return n=null==n?9007199254740991:n,!!n&&(typeof t=="number"||dt.test(t))&&t>-1&&0==t%1&&n>t}function Jr(t,n,r){if(!ze(r))return false;var e=typeof n;return("number"==e?We(r)&&Gr(n,r.length):"string"==e&&n in r)?Se(r[n],t):false;
+}function Yr(t,n){if(li(t))return false;var r=typeof t;return"number"==r||"symbol"==r||"boolean"==r||null==t||Te(t)?true:nt.test(t)||!tt.test(t)||null!=n&&t in Object(n)}function Hr(t){var n=typeof t;return"string"==n||"number"==n||"symbol"==n||"boolean"==n?"__proto__"!==t:null===t}function Qr(t){var n=Cr(t),r=jt[n];return typeof r=="function"&&n in Lt.prototype?t===r?true:(n=Ao(r),!!n&&t===n[0]):false}function Xr(t){var n=t&&t.constructor;return t===(typeof n=="function"&&n.prototype||xu)}function te(t,n){return function(r){
+return null==r?false:r[t]===n&&(n!==N||t in Object(r))}}function ne(t,n,r,e,u,o){return ze(t)&&ze(n)&&Rn(t,n,N,ne,o.set(n,t)),t}function re(t,n){return 1==n.length?t:_n(t,Pn(n,0,-1))}function ee(t){if(typeof t=="string"||Te(t))return t;var n=t+"";return"0"==n&&1/t==-P?"-0":n}function ue(t){if(null!=t){try{return mu.call(t)}catch(n){}return t+""}return""}function oe(t){if(t instanceof Lt)return t.clone();var n=new wt(t.__wrapped__,t.__chain__);return n.__actions__=cr(t.__actions__),n.__index__=t.__index__,
+n.__values__=t.__values__,n}function ie(t,n,r){var e=t?t.length:0;return e?(n=r||n===N?1:Ke(n),Pn(t,0>n?0:n,e)):[]}function fe(t,n,r){var e=t?t.length:0;return e?(n=r||n===N?1:Ke(n),n=e-n,Pn(t,0,0>n?0:n)):[]}function ce(t){return t&&t.length?t[0]:N}function ae(t){var n=t?t.length:0;return n?t[n-1]:N}function le(t,n){return t&&t.length&&n&&n.length?zn(t,n):t}function se(t){return t?Qu.call(t):t}function he(t){if(!t||!t.length)return[];var n=0;return t=i(t,function(t){return Be(t)?(n=Ku(t.length,n),
+!0):void 0}),m(n,function(n){return a(t,Mn(n))})}function pe(t,n){if(!t||!t.length)return[];var e=he(t);return null==n?e:a(e,function(t){return r(n,N,t)})}function _e(t){return t=jt(t),t.__chain__=true,t}function ve(t,n){return n(t)}function ge(){return this}function de(t,n){return typeof n=="function"&&li(t)?u(t,n):yo(t,Mr(n))}function ye(t,n){var r;if(typeof n=="function"&&li(t)){for(r=t.length;r--&&false!==n(t[r],r,t););r=t}else r=bo(t,Mr(n));return r}function be(t,n){return(li(t)?a:En)(t,Mr(n,3))}function xe(t,n,r){
+var e=-1,u=Ve(t),o=u.length,i=o-1;for(n=(r?Jr(t,n,r):n===N)?1:tn(Ke(n),0,o);++e<n;)t=$n(e,i),r=u[t],u[t]=u[e],u[e]=r;return u.length=n,u}function je(t,n,r){return n=r?N:n,n=t&&null==n?t.length:n,Wr(t,128,N,N,N,N,n)}function me(t,n){var r;if(typeof n!="function")throw new yu("Expected a function");return t=Ke(t),function(){return 0<--t&&(r=n.apply(this,arguments)),1>=t&&(n=N),r}}function we(t,n,r){return n=r?N:n,t=Wr(t,8,N,N,N,N,N,n),t.placeholder=we.placeholder,t}function Ae(t,n,r){return n=r?N:n,
+t=Wr(t,16,N,N,N,N,N,n),t.placeholder=Ae.placeholder,t}function Oe(t,n,r){function e(n){var r=c,e=a;return c=a=N,_=n,s=t.apply(e,r)}function u(t){var r=t-p;return t-=_,!p||r>=n||0>r||g&&t>=l}function o(){var t=Xo();if(u(t))return i(t);var r;r=t-_,t=n-(t-p),r=g?Gu(t,l-r):t,h=$u(o,r)}function i(t){return Lu(h),h=N,d&&c?e(t):(c=a=N,s)}function f(){var t=Xo(),r=u(t);if(c=arguments,a=this,p=t,r){if(h===N)return _=t=p,h=$u(o,n),v?e(t):s;if(g)return Lu(h),h=$u(o,n),e(p)}return h===N&&(h=$u(o,n)),s}var c,a,l,s,h,p=0,_=0,v=false,g=false,d=true;
+if(typeof t!="function")throw new yu("Expected a function");return n=Je(n)||0,ze(r)&&(v=!!r.leading,l=(g="maxWait"in r)?Ku(Je(r.maxWait)||0,n):l,d="trailing"in r?!!r.trailing:d),f.cancel=function(){h!==N&&Lu(h),p=_=0,c=a=h=N},f.flush=function(){return h===N?s:i(Xo())},f}function ke(t,n){function r(){var e=arguments,u=n?n.apply(this,e):e[0],o=r.cache;return o.has(u)?o.get(u):(e=t.apply(this,e),r.cache=o.set(u,e),e)}if(typeof t!="function"||n&&typeof n!="function")throw new yu("Expected a function");
+return r.cache=new(ke.Cache||Mt),r}function Ee(t,n){if(typeof t!="function")throw new yu("Expected a function");return n=Ku(n===N?t.length-1:Ke(n),0),function(){for(var e=arguments,u=-1,o=Ku(e.length-n,0),i=Array(o);++u<o;)i[u]=e[n+u];switch(n){case 0:return t.call(this,i);case 1:return t.call(this,e[0],i);case 2:return t.call(this,e[0],e[1],i)}for(o=Array(n+1),u=-1;++u<n;)o[u]=e[u];return o[n]=i,r(t,this,o)}}function Ie(){if(!arguments.length)return[];var t=arguments[0];return li(t)?t:[t]}function Se(t,n){
+return t===n||t!==t&&n!==n}function Re(t){return Be(t)&&wu.call(t,"callee")&&(!Du.call(t,"callee")||"[object Arguments]"==ku.call(t))}function We(t){return null!=t&&Ue(Oo(t))&&!Ce(t)}function Be(t){return De(t)&&We(t)}function Le(t){return De(t)?"[object Error]"==ku.call(t)||typeof t.message=="string"&&typeof t.name=="string":false}function Ce(t){return t=ze(t)?ku.call(t):"","[object Function]"==t||"[object GeneratorFunction]"==t}function Me(t){return typeof t=="number"&&t==Ke(t)}function Ue(t){return typeof t=="number"&&t>-1&&0==t%1&&9007199254740991>=t;
+}function ze(t){var n=typeof t;return!!t&&("object"==n||"function"==n)}function De(t){return!!t&&typeof t=="object"}function $e(t){return ze(t)?(Ce(t)||L(t)?Iu:vt).test(ue(t)):false}function Fe(t){return typeof t=="number"||De(t)&&"[object Number]"==ku.call(t)}function Ne(t){return!De(t)||"[object Object]"!=ku.call(t)||L(t)?false:(t=Zu(Object(t)),null===t?true:(t=wu.call(t,"constructor")&&t.constructor,typeof t=="function"&&t instanceof t&&mu.call(t)==Ou))}function Pe(t){return ze(t)&&"[object RegExp]"==ku.call(t);
+}function Ze(t){return typeof t=="string"||!li(t)&&De(t)&&"[object String]"==ku.call(t)}function Te(t){return typeof t=="symbol"||De(t)&&"[object Symbol]"==ku.call(t)}function qe(t){return De(t)&&Ue(t.length)&&!!Wt[ku.call(t)]}function Ve(t){if(!t)return[];if(We(t))return Ze(t)?t.match(kt):cr(t);if(Uu&&t[Uu])return C(t[Uu]());var n=Fr(t);return("[object Map]"==n?M:"[object Set]"==n?z:uu)(t)}function Ke(t){if(!t)return 0===t?t:0;if(t=Je(t),t===P||t===-P)return 1.7976931348623157e308*(0>t?-1:1);var n=t%1;
+return t===t?n?t-n:t:0}function Ge(t){return t?tn(Ke(t),0,4294967295):0}function Je(t){if(typeof t=="number")return t;if(Te(t))return Z;if(ze(t)&&(t=Ce(t.valueOf)?t.valueOf():t,t=ze(t)?t+"":t),typeof t!="string")return 0===t?t:+t;t=t.replace(ot,"");var n=_t.test(t);return n||gt.test(t)?$t(t.slice(2),n?2:8):pt.test(t)?Z:+t}function Ye(t){return ar(t,nu(t))}function He(t){return null==t?"":Gn(t)}function Qe(t,n,r){return t=null==t?N:_n(t,n),t===N?r:t}function Xe(t,n){return null!=t&&Nr(t,n,yn)}function tu(t){
+var n=Xr(t);if(!n&&!We(t))return Vu(Object(t));var r,e=qr(t),u=!!e,e=e||[],o=e.length;for(r in t)!dn(t,r)||u&&("length"==r||Gr(r,o))||n&&"constructor"==r||e.push(r);return e}function nu(t){for(var n=-1,r=Xr(t),e=On(t),u=e.length,o=qr(t),i=!!o,o=o||[],f=o.length;++n<u;){var c=e[n];i&&("length"==c||Gr(c,f))||"constructor"==c&&(r||!wu.call(t,c))||o.push(c)}return o}function ru(t){return w(t,tu(t))}function eu(t){return w(t,nu(t))}function uu(t){return t?O(t,tu(t)):[]}function ou(t){return Mi(He(t).toLowerCase());
+}function iu(t){return(t=He(t))&&t.replace(yt,S).replace(Ot,"")}function fu(t,n,r){return t=He(t),n=r?N:n,n===N&&(n=St.test(t)?Et:ct),t.match(n)||[]}function cu(t){return function(){return t}}function au(t){return t}function lu(t){return An(typeof t=="function"?t:nn(t,true))}function su(t,n,r){var e=tu(n),o=pn(n,e);null!=r||ze(n)&&(o.length||!e.length)||(r=n,n=t,t=this,o=pn(n,tu(n)));var i=!(ze(r)&&"chain"in r&&!r.chain),f=Ce(t);return u(o,function(r){var e=n[r];t[r]=e,f&&(t.prototype[r]=function(){
+var n=this.__chain__;if(i||n){var r=t(this.__wrapped__);return(r.__actions__=cr(this.__actions__)).push({func:e,args:arguments,thisArg:t}),r.__chain__=n,r}return e.apply(t,l([this.value()],arguments))})}),t}function hu(){}function pu(t){return Yr(t)?Mn(ee(t)):Un(t)}I=I?Kt.defaults({},I,Kt.pick(Vt,Rt)):Vt;var _u=I.Date,vu=I.Error,gu=I.Math,du=I.RegExp,yu=I.TypeError,bu=I.Array.prototype,xu=I.Object.prototype,ju=I.String.prototype,mu=I.Function.prototype.toString,wu=xu.hasOwnProperty,Au=0,Ou=mu.call(Object),ku=xu.toString,Eu=Vt._,Iu=du("^"+mu.call(wu).replace(et,"\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,"$1.*?")+"$"),Su=Pt?I.Buffer:N,Ru=I.Reflect,Wu=I.Symbol,Bu=I.Uint8Array,Lu=I.clearTimeout,Cu=Ru?Ru.f:N,Mu=Object.getOwnPropertySymbols,Uu=typeof(Uu=Wu&&Wu.iterator)=="symbol"?Uu:N,zu=Object.create,Du=xu.propertyIsEnumerable,$u=I.setTimeout,Fu=bu.splice,Nu=gu.ceil,Pu=gu.floor,Zu=Object.getPrototypeOf,Tu=I.isFinite,qu=bu.join,Vu=Object.keys,Ku=gu.max,Gu=gu.min,Ju=I.parseInt,Yu=gu.random,Hu=ju.replace,Qu=bu.reverse,Xu=ju.split,to=zr(I,"DataView"),no=zr(I,"Map"),ro=zr(I,"Promise"),eo=zr(I,"Set"),uo=zr(I,"WeakMap"),oo=zr(Object,"create"),io=uo&&new uo,fo=!Du.call({
+valueOf:1},"valueOf"),co={},ao=ue(to),lo=ue(no),so=ue(ro),ho=ue(eo),po=ue(uo),_o=Wu?Wu.prototype:N,vo=_o?_o.valueOf:N,go=_o?_o.toString:N;jt.templateSettings={escape:H,evaluate:Q,interpolate:X,variable:"",imports:{_:jt}},jt.prototype=mt.prototype,jt.prototype.constructor=jt,wt.prototype=en(mt.prototype),wt.prototype.constructor=wt,Lt.prototype=en(mt.prototype),Lt.prototype.constructor=Lt,Ct.prototype=oo?oo(null):xu,Mt.prototype.clear=function(){this.__data__={hash:new Ct,map:no?new no:[],string:new Ct
+}},Mt.prototype["delete"]=function(t){var n=this.__data__;return Hr(t)?(n=typeof t=="string"?n.string:n.hash,t=(oo?n[t]!==N:wu.call(n,t))&&delete n[t]):t=no?n.map["delete"](t):Nt(n.map,t),t},Mt.prototype.get=function(t){var n=this.__data__;return Hr(t)?(n=typeof t=="string"?n.string:n.hash,oo?(t=n[t],t="__lodash_hash_undefined__"===t?N:t):t=wu.call(n,t)?n[t]:N):t=no?n.map.get(t):Zt(n.map,t),t},Mt.prototype.has=function(t){var n=this.__data__;return Hr(t)?(n=typeof t=="string"?n.string:n.hash,t=oo?n[t]!==N:wu.call(n,t)):t=no?n.map.has(t):-1<Tt(n.map,t),
+t},Mt.prototype.set=function(t,n){var r=this.__data__;return Hr(t)?(typeof t=="string"?r.string:r.hash)[t]=oo&&n===N?"__lodash_hash_undefined__":n:no?r.map.set(t,n):qt(r.map,t,n),this},Ut.prototype.push=function(t){var n=this.__data__;Hr(t)?(n=n.__data__,(typeof t=="string"?n.string:n.hash)[t]="__lodash_hash_undefined__"):n.set(t,"__lodash_hash_undefined__")},Ft.prototype.clear=function(){this.__data__={array:[],map:null}},Ft.prototype["delete"]=function(t){var n=this.__data__,r=n.array;return r?Nt(r,t):n.map["delete"](t);
+},Ft.prototype.get=function(t){var n=this.__data__,r=n.array;return r?Zt(r,t):n.map.get(t)},Ft.prototype.has=function(t){var n=this.__data__,r=n.array;return r?-1<Tt(r,t):n.map.has(t)},Ft.prototype.set=function(t,n){var r=this.__data__,e=r.array;return e&&(199>e.length?qt(e,t,n):(r.array=null,r.map=new Mt(e))),(r=r.map)&&r.set(t,n),this};var yo=pr(sn),bo=pr(hn,true),xo=_r(),jo=_r(true);Cu&&!Du.call({valueOf:1},"valueOf")&&(On=function(t){return C(Cu(t))});var mo=io?function(t,n){return io.set(t,n),t}:au,wo=eo&&1/z(new eo([,-0]))[1]==P?function(t){
+return new eo(t)}:hu,Ao=io?function(t){return io.get(t)}:hu,Oo=Mn("length");Mu||($r=function(){return[]});var ko=Mu?function(t){for(var n=[];t;)l(n,$r(t)),t=Zu(Object(t));return n}:$r;(to&&"[object DataView]"!=Fr(new to(new ArrayBuffer(1)))||no&&"[object Map]"!=Fr(new no)||ro&&"[object Promise]"!=Fr(ro.resolve())||eo&&"[object Set]"!=Fr(new eo)||uo&&"[object WeakMap]"!=Fr(new uo))&&(Fr=function(t){var n=ku.call(t);if(t=(t="[object Object]"==n?t.constructor:N)?ue(t):N)switch(t){case ao:return"[object DataView]";
+case lo:return"[object Map]";case so:return"[object Promise]";case ho:return"[object Set]";case po:return"[object WeakMap]"}return n});var Eo=function(){var t=0,n=0;return function(r,e){var u=Xo(),o=16-(u-n);if(n=u,o>0){if(150<=++t)return r}else t=0;return mo(r,e)}}(),Io=ke(function(t){var n=[];return He(t).replace(rt,function(t,r,e,u){n.push(e?u.replace(at,"$1"):r||t)}),n}),So=Ee(function(t,n){return Be(t)?on(t,ln(n,1,Be,true)):[]}),Ro=Ee(function(t,n){var r=ae(n);return Be(r)&&(r=N),Be(t)?on(t,ln(n,1,Be,true),Mr(r)):[];
+}),Wo=Ee(function(t,n){var r=ae(n);return Be(r)&&(r=N),Be(t)?on(t,ln(n,1,Be,true),N,r):[]}),Bo=Ee(function(t){var n=a(t,tr);return n.length&&n[0]===t[0]?bn(n):[]}),Lo=Ee(function(t){var n=ae(t),r=a(t,tr);return n===ae(r)?n=N:r.pop(),r.length&&r[0]===t[0]?bn(r,Mr(n)):[]}),Co=Ee(function(t){var n=ae(t),r=a(t,tr);return n===ae(r)?n=N:r.pop(),r.length&&r[0]===t[0]?bn(r,N,n):[]}),Mo=Ee(le),Uo=Ee(function(t,n){n=ln(n,1);var r=t?t.length:0,e=Xt(t,n);return Dn(t,a(n,function(t){return Gr(t,r)?+t:t}).sort(or)),
+e}),zo=Ee(function(t){return Jn(ln(t,1,Be,true))}),Do=Ee(function(t){var n=ae(t);return Be(n)&&(n=N),Jn(ln(t,1,Be,true),Mr(n))}),$o=Ee(function(t){var n=ae(t);return Be(n)&&(n=N),Jn(ln(t,1,Be,true),N,n)}),Fo=Ee(function(t,n){return Be(t)?on(t,n):[]}),No=Ee(function(t){return Qn(i(t,Be))}),Po=Ee(function(t){var n=ae(t);return Be(n)&&(n=N),Qn(i(t,Be),Mr(n))}),Zo=Ee(function(t){var n=ae(t);return Be(n)&&(n=N),Qn(i(t,Be),N,n)}),To=Ee(he),qo=Ee(function(t){var n=t.length,n=n>1?t[n-1]:N,n=typeof n=="function"?(t.pop(),
+n):N;return pe(t,n)}),Vo=Ee(function(t){function n(n){return Xt(n,t)}t=ln(t,1);var r=t.length,e=r?t[0]:0,u=this.__wrapped__;return!(r>1||this.__actions__.length)&&u instanceof Lt&&Gr(e)?(u=u.slice(e,+e+(r?1:0)),u.__actions__.push({func:ve,args:[n],thisArg:N}),new wt(u,this.__chain__).thru(function(t){return r&&!t.length&&t.push(N),t})):this.thru(n)}),Ko=sr(function(t,n,r){wu.call(t,r)?++t[r]:t[r]=1}),Go=sr(function(t,n,r){wu.call(t,r)?t[r].push(n):t[r]=[n]}),Jo=Ee(function(t,n,e){var u=-1,o=typeof n=="function",i=Yr(n),f=We(t)?Array(t.length):[];
+return yo(t,function(t){var c=o?n:i&&null!=t?t[n]:N;f[++u]=c?r(c,t,e):jn(t,n,e)}),f}),Yo=sr(function(t,n,r){t[r]=n}),Ho=sr(function(t,n,r){t[r?0:1].push(n)},function(){return[[],[]]}),Qo=Ee(function(t,n){if(null==t)return[];var r=n.length;return r>1&&Jr(t,n[0],n[1])?n=[]:r>2&&Jr(n[0],n[1],n[2])&&(n=[n[0]]),n=1==n.length&&li(n[0])?n[0]:ln(n,1,Kr),Bn(t,n,[])}),Xo=_u.now,ti=Ee(function(t,n,r){var e=1;if(r.length)var u=U(r,Dr(ti)),e=32|e;return Wr(t,e,n,r,u)}),ni=Ee(function(t,n,r){var e=3;if(r.length)var u=U(r,Dr(ni)),e=32|e;
+return Wr(n,e,t,r,u)}),ri=Ee(function(t,n){return un(t,1,n)}),ei=Ee(function(t,n,r){return un(t,Je(n)||0,r)});ke.Cache=Mt;var ui=Ee(function(t,n){n=1==n.length&&li(n[0])?a(n[0],A(Mr())):a(ln(n,1,Kr),A(Mr()));var e=n.length;return Ee(function(u){for(var o=-1,i=Gu(u.length,e);++o<i;)u[o]=n[o].call(this,u[o]);return r(t,this,u)})}),oi=Ee(function(t,n){var r=U(n,Dr(oi));return Wr(t,32,N,n,r)}),ii=Ee(function(t,n){var r=U(n,Dr(ii));return Wr(t,64,N,n,r)}),fi=Ee(function(t,n){return Wr(t,256,N,N,N,ln(n,1));
+}),ci=Ir(gn),ai=Ir(function(t,n){return t>=n}),li=Array.isArray,si=Su?function(t){return t instanceof Su}:cu(false),hi=Ir(kn),pi=Ir(function(t,n){return n>=t}),_i=hr(function(t,n){if(fo||Xr(n)||We(n))ar(n,tu(n),t);else for(var r in n)wu.call(n,r)&&Yt(t,r,n[r])}),vi=hr(function(t,n){if(fo||Xr(n)||We(n))ar(n,nu(n),t);else for(var r in n)Yt(t,r,n[r])}),gi=hr(function(t,n,r,e){ar(n,nu(n),t,e)}),di=hr(function(t,n,r,e){ar(n,tu(n),t,e)}),yi=Ee(function(t,n){return Xt(t,ln(n,1))}),bi=Ee(function(t){return t.push(N,Gt),
+r(gi,N,t)}),xi=Ee(function(t){return t.push(N,ne),r(Oi,N,t)}),ji=mr(function(t,n,r){t[n]=r},cu(au)),mi=mr(function(t,n,r){wu.call(t,n)?t[n].push(r):t[n]=[r]},Mr),wi=Ee(jn),Ai=hr(function(t,n,r){Rn(t,n,r)}),Oi=hr(function(t,n,r,e){Rn(t,n,r,e)}),ki=Ee(function(t,n){return null==t?{}:(n=a(ln(n,1),ee),Ln(t,on(vn(t,nu,ko),n)))}),Ei=Ee(function(t,n){return null==t?{}:Ln(t,a(ln(n,1),ee))}),Ii=dr(function(t,n,r){return n=n.toLowerCase(),t+(r?ou(n):n)}),Si=dr(function(t,n,r){return t+(r?"-":"")+n.toLowerCase();
+}),Ri=dr(function(t,n,r){return t+(r?" ":"")+n.toLowerCase()}),Wi=gr("toLowerCase"),Bi=dr(function(t,n,r){return t+(r?"_":"")+n.toLowerCase()}),Li=dr(function(t,n,r){return t+(r?" ":"")+Mi(n)}),Ci=dr(function(t,n,r){return t+(r?" ":"")+n.toUpperCase()}),Mi=gr("toUpperCase"),Ui=Ee(function(t,n){try{return r(t,N,n)}catch(e){return Le(e)?e:new vu(e)}}),zi=Ee(function(t,n){return u(ln(n,1),function(n){n=ee(n),t[n]=ti(t[n],t)}),t}),Di=xr(),$i=xr(true),Fi=Ee(function(t,n){return function(r){return jn(r,t,n);
+}}),Ni=Ee(function(t,n){return function(r){return jn(t,r,n)}}),Pi=Ar(a),Zi=Ar(o),Ti=Ar(p),qi=Er(),Vi=Er(true),Ki=wr(function(t,n){return t+n}),Gi=Rr("ceil"),Ji=wr(function(t,n){return t/n}),Yi=Rr("floor"),Hi=wr(function(t,n){return t*n}),Qi=Rr("round"),Xi=wr(function(t,n){return t-n});return jt.after=function(t,n){if(typeof n!="function")throw new yu("Expected a function");return t=Ke(t),function(){return 1>--t?n.apply(this,arguments):void 0}},jt.ary=je,jt.assign=_i,jt.assignIn=vi,jt.assignInWith=gi,
+jt.assignWith=di,jt.at=yi,jt.before=me,jt.bind=ti,jt.bindAll=zi,jt.bindKey=ni,jt.castArray=Ie,jt.chain=_e,jt.chunk=function(t,n,r){if(n=(r?Jr(t,n,r):n===N)?1:Ku(Ke(n),0),r=t?t.length:0,!r||1>n)return[];for(var e=0,u=0,o=Array(Nu(r/n));r>e;)o[u++]=Pn(t,e,e+=n);return o},jt.compact=function(t){for(var n=-1,r=t?t.length:0,e=0,u=[];++n<r;){var o=t[n];o&&(u[e++]=o)}return u},jt.concat=function(){var t=arguments.length,n=Ie(arguments[0]);if(2>t)return t?cr(n):[];for(var r=Array(t-1);t--;)r[t-1]=arguments[t];
+for(var t=ln(r,1),r=-1,e=n.length,u=-1,o=t.length,i=Array(e+o);++r<e;)i[r]=n[r];for(;++u<o;)i[r++]=t[u];return i},jt.cond=function(t){var n=t?t.length:0,e=Mr();return t=n?a(t,function(t){if("function"!=typeof t[1])throw new yu("Expected a function");return[e(t[0]),t[1]]}):[],Ee(function(e){for(var u=-1;++u<n;){var o=t[u];if(r(o[0],this,e))return r(o[1],this,e)}})},jt.conforms=function(t){return rn(nn(t,true))},jt.constant=cu,jt.countBy=Ko,jt.create=function(t,n){var r=en(t);return n?Qt(r,n):r},jt.curry=we,
+jt.curryRight=Ae,jt.debounce=Oe,jt.defaults=bi,jt.defaultsDeep=xi,jt.defer=ri,jt.delay=ei,jt.difference=So,jt.differenceBy=Ro,jt.differenceWith=Wo,jt.drop=ie,jt.dropRight=fe,jt.dropRightWhile=function(t,n){return t&&t.length?Yn(t,Mr(n,3),true,true):[]},jt.dropWhile=function(t,n){return t&&t.length?Yn(t,Mr(n,3),true):[]},jt.fill=function(t,n,r,e){var u=t?t.length:0;if(!u)return[];for(r&&typeof r!="number"&&Jr(t,n,r)&&(r=0,e=u),u=t.length,r=Ke(r),0>r&&(r=-r>u?0:u+r),e=e===N||e>u?u:Ke(e),0>e&&(e+=u),e=r>e?0:Ge(e);e>r;)t[r++]=n;
+return t},jt.filter=function(t,n){return(li(t)?i:an)(t,Mr(n,3))},jt.flatMap=function(t,n){return ln(be(t,n),1)},jt.flatMapDeep=function(t,n){return ln(be(t,n),P)},jt.flatMapDepth=function(t,n,r){return r=r===N?1:Ke(r),ln(be(t,n),r)},jt.flatten=function(t){return t&&t.length?ln(t,1):[]},jt.flattenDeep=function(t){return t&&t.length?ln(t,P):[]},jt.flattenDepth=function(t,n){return t&&t.length?(n=n===N?1:Ke(n),ln(t,n)):[]},jt.flip=function(t){return Wr(t,512)},jt.flow=Di,jt.flowRight=$i,jt.fromPairs=function(t){
+for(var n=-1,r=t?t.length:0,e={};++n<r;){var u=t[n];e[u[0]]=u[1]}return e},jt.functions=function(t){return null==t?[]:pn(t,tu(t))},jt.functionsIn=function(t){return null==t?[]:pn(t,nu(t))},jt.groupBy=Go,jt.initial=function(t){return fe(t,1)},jt.intersection=Bo,jt.intersectionBy=Lo,jt.intersectionWith=Co,jt.invert=ji,jt.invertBy=mi,jt.invokeMap=Jo,jt.iteratee=lu,jt.keyBy=Yo,jt.keys=tu,jt.keysIn=nu,jt.map=be,jt.mapKeys=function(t,n){var r={};return n=Mr(n,3),sn(t,function(t,e,u){r[n(t,e,u)]=t}),r},
+jt.mapValues=function(t,n){var r={};return n=Mr(n,3),sn(t,function(t,e,u){r[e]=n(t,e,u)}),r},jt.matches=function(t){return In(nn(t,true))},jt.matchesProperty=function(t,n){return Sn(t,nn(n,true))},jt.memoize=ke,jt.merge=Ai,jt.mergeWith=Oi,jt.method=Fi,jt.methodOf=Ni,jt.mixin=su,jt.negate=function(t){if(typeof t!="function")throw new yu("Expected a function");return function(){return!t.apply(this,arguments)}},jt.nthArg=function(t){return t=Ke(t),Ee(function(n){return Wn(n,t)})},jt.omit=ki,jt.omitBy=function(t,n){
+return n=Mr(n),Cn(t,function(t,r){return!n(t,r)})},jt.once=function(t){return me(2,t)},jt.orderBy=function(t,n,r,e){return null==t?[]:(li(n)||(n=null==n?[]:[n]),r=e?N:r,li(r)||(r=null==r?[]:[r]),Bn(t,n,r))},jt.over=Pi,jt.overArgs=ui,jt.overEvery=Zi,jt.overSome=Ti,jt.partial=oi,jt.partialRight=ii,jt.partition=Ho,jt.pick=Ei,jt.pickBy=function(t,n){return null==t?{}:Cn(t,Mr(n))},jt.property=pu,jt.propertyOf=function(t){return function(n){return null==t?N:_n(t,n)}},jt.pull=Mo,jt.pullAll=le,jt.pullAllBy=function(t,n,r){
+return t&&t.length&&n&&n.length?zn(t,n,Mr(r)):t},jt.pullAllWith=function(t,n,r){return t&&t.length&&n&&n.length?zn(t,n,N,r):t},jt.pullAt=Uo,jt.range=qi,jt.rangeRight=Vi,jt.rearg=fi,jt.reject=function(t,n){var r=li(t)?i:an;return n=Mr(n,3),r(t,function(t,r,e){return!n(t,r,e)})},jt.remove=function(t,n){var r=[];if(!t||!t.length)return r;var e=-1,u=[],o=t.length;for(n=Mr(n,3);++e<o;){var i=t[e];n(i,e,t)&&(r.push(i),u.push(e))}return Dn(t,u),r},jt.rest=Ee,jt.reverse=se,jt.sampleSize=xe,jt.set=function(t,n,r){
+return null==t?t:Nn(t,n,r)},jt.setWith=function(t,n,r,e){return e=typeof e=="function"?e:N,null==t?t:Nn(t,n,r,e)},jt.shuffle=function(t){return xe(t,4294967295)},jt.slice=function(t,n,r){var e=t?t.length:0;return e?(r&&typeof r!="number"&&Jr(t,n,r)?(n=0,r=e):(n=null==n?0:Ke(n),r=r===N?e:Ke(r)),Pn(t,n,r)):[]},jt.sortBy=Qo,jt.sortedUniq=function(t){return t&&t.length?Vn(t):[]},jt.sortedUniqBy=function(t,n){return t&&t.length?Vn(t,Mr(n)):[]},jt.split=function(t,n,r){return r&&typeof r!="number"&&Jr(t,n,r)&&(n=r=N),
+r=r===N?4294967295:r>>>0,r?(t=He(t))&&(typeof n=="string"||null!=n&&!Pe(n))&&(n=Gn(n),""==n&&It.test(t))?rr(t.match(kt),0,r):Xu.call(t,n,r):[]},jt.spread=function(t,n){if(typeof t!="function")throw new yu("Expected a function");return n=n===N?0:Ku(Ke(n),0),Ee(function(e){var u=e[n];return e=rr(e,0,n),u&&l(e,u),r(t,this,e)})},jt.tail=function(t){return ie(t,1)},jt.take=function(t,n,r){return t&&t.length?(n=r||n===N?1:Ke(n),Pn(t,0,0>n?0:n)):[]},jt.takeRight=function(t,n,r){var e=t?t.length:0;return e?(n=r||n===N?1:Ke(n),
+n=e-n,Pn(t,0>n?0:n,e)):[]},jt.takeRightWhile=function(t,n){return t&&t.length?Yn(t,Mr(n,3),false,true):[]},jt.takeWhile=function(t,n){return t&&t.length?Yn(t,Mr(n,3)):[]},jt.tap=function(t,n){return n(t),t},jt.throttle=function(t,n,r){var e=true,u=true;if(typeof t!="function")throw new yu("Expected a function");return ze(r)&&(e="leading"in r?!!r.leading:e,u="trailing"in r?!!r.trailing:u),Oe(t,n,{leading:e,maxWait:n,trailing:u})},jt.thru=ve,jt.toArray=Ve,jt.toPairs=ru,jt.toPairsIn=eu,jt.toPath=function(t){
+return li(t)?a(t,ee):Te(t)?[t]:cr(Io(t))},jt.toPlainObject=Ye,jt.transform=function(t,n,r){var e=li(t)||qe(t);if(n=Mr(n,4),null==r)if(e||ze(t)){var o=t.constructor;r=e?li(t)?new o:[]:Ce(o)?en(Zu(Object(t))):{}}else r={};return(e?u:sn)(t,function(t,e,u){return n(r,t,e,u)}),r},jt.unary=function(t){return je(t,1)},jt.union=zo,jt.unionBy=Do,jt.unionWith=$o,jt.uniq=function(t){return t&&t.length?Jn(t):[]},jt.uniqBy=function(t,n){return t&&t.length?Jn(t,Mr(n)):[]},jt.uniqWith=function(t,n){return t&&t.length?Jn(t,N,n):[];
+},jt.unset=function(t,n){var r;if(null==t)r=true;else{r=t;var e=n,e=Yr(e,r)?[e]:nr(e);r=re(r,e),e=ee(ae(e)),r=!(null!=r&&dn(r,e))||delete r[e]}return r},jt.unzip=he,jt.unzipWith=pe,jt.update=function(t,n,r){return null==t?t:Nn(t,n,(typeof r=="function"?r:au)(_n(t,n)),void 0)},jt.updateWith=function(t,n,r,e){return e=typeof e=="function"?e:N,null!=t&&(t=Nn(t,n,(typeof r=="function"?r:au)(_n(t,n)),e)),t},jt.values=uu,jt.valuesIn=function(t){return null==t?[]:O(t,nu(t))},jt.without=Fo,jt.words=fu,jt.wrap=function(t,n){
+return n=null==n?au:n,oi(n,t)},jt.xor=No,jt.xorBy=Po,jt.xorWith=Zo,jt.zip=To,jt.zipObject=function(t,n){return Xn(t||[],n||[],Yt)},jt.zipObjectDeep=function(t,n){return Xn(t||[],n||[],Nn)},jt.zipWith=qo,jt.entries=ru,jt.entriesIn=eu,jt.extend=vi,jt.extendWith=gi,su(jt,jt),jt.add=Ki,jt.attempt=Ui,jt.camelCase=Ii,jt.capitalize=ou,jt.ceil=Gi,jt.clamp=function(t,n,r){return r===N&&(r=n,n=N),r!==N&&(r=Je(r),r=r===r?r:0),n!==N&&(n=Je(n),n=n===n?n:0),tn(Je(t),n,r)},jt.clone=function(t){return nn(t,false,true);
+},jt.cloneDeep=function(t){return nn(t,true,true)},jt.cloneDeepWith=function(t,n){return nn(t,true,true,n)},jt.cloneWith=function(t,n){return nn(t,false,true,n)},jt.deburr=iu,jt.divide=Ji,jt.endsWith=function(t,n,r){t=He(t),n=Gn(n);var e=t.length;return r=r===N?e:tn(Ke(r),0,e),r-=n.length,r>=0&&t.indexOf(n,r)==r},jt.eq=Se,jt.escape=function(t){return(t=He(t))&&Y.test(t)?t.replace(G,R):t},jt.escapeRegExp=function(t){return(t=He(t))&&ut.test(t)?t.replace(et,"\\$&"):t},jt.every=function(t,n,r){var e=li(t)?o:fn;return r&&Jr(t,n,r)&&(n=N),
+e(t,Mr(n,3))},jt.find=function(t,n){if(n=Mr(n,3),li(t)){var r=v(t,n);return r>-1?t[r]:N}return _(t,n,yo)},jt.findIndex=function(t,n){return t&&t.length?v(t,Mr(n,3)):-1},jt.findKey=function(t,n){return _(t,Mr(n,3),sn,true)},jt.findLast=function(t,n){if(n=Mr(n,3),li(t)){var r=v(t,n,true);return r>-1?t[r]:N}return _(t,n,bo)},jt.findLastIndex=function(t,n){return t&&t.length?v(t,Mr(n,3),true):-1},jt.findLastKey=function(t,n){return _(t,Mr(n,3),hn,true)},jt.floor=Yi,jt.forEach=de,jt.forEachRight=ye,jt.forIn=function(t,n){
+return null==t?t:xo(t,Mr(n),nu)},jt.forInRight=function(t,n){return null==t?t:jo(t,Mr(n),nu)},jt.forOwn=function(t,n){return t&&sn(t,Mr(n))},jt.forOwnRight=function(t,n){return t&&hn(t,Mr(n))},jt.get=Qe,jt.gt=ci,jt.gte=ai,jt.has=function(t,n){return null!=t&&Nr(t,n,dn)},jt.hasIn=Xe,jt.head=ce,jt.identity=au,jt.includes=function(t,n,r,e){return t=We(t)?t:uu(t),r=r&&!e?Ke(r):0,e=t.length,0>r&&(r=Ku(e+r,0)),Ze(t)?e>=r&&-1<t.indexOf(n,r):!!e&&-1<g(t,n,r)},jt.indexOf=function(t,n,r){var e=t?t.length:0;
+return e?(r=Ke(r),0>r&&(r=Ku(e+r,0)),g(t,n,r)):-1},jt.inRange=function(t,n,r){return n=Je(n)||0,r===N?(r=n,n=0):r=Je(r)||0,t=Je(t),t>=Gu(n,r)&&t<Ku(n,r)},jt.invoke=wi,jt.isArguments=Re,jt.isArray=li,jt.isArrayBuffer=function(t){return De(t)&&"[object ArrayBuffer]"==ku.call(t)},jt.isArrayLike=We,jt.isArrayLikeObject=Be,jt.isBoolean=function(t){return true===t||false===t||De(t)&&"[object Boolean]"==ku.call(t)},jt.isBuffer=si,jt.isDate=function(t){return De(t)&&"[object Date]"==ku.call(t)},jt.isElement=function(t){
+return!!t&&1===t.nodeType&&De(t)&&!Ne(t)},jt.isEmpty=function(t){if(We(t)&&(li(t)||Ze(t)||Ce(t.splice)||Re(t)||si(t)))return!t.length;if(De(t)){var n=Fr(t);if("[object Map]"==n||"[object Set]"==n)return!t.size}for(var r in t)if(wu.call(t,r))return false;return!(fo&&tu(t).length)},jt.isEqual=function(t,n){return mn(t,n)},jt.isEqualWith=function(t,n,r){var e=(r=typeof r=="function"?r:N)?r(t,n):N;return e===N?mn(t,n,r):!!e},jt.isError=Le,jt.isFinite=function(t){return typeof t=="number"&&Tu(t)},jt.isFunction=Ce,
+jt.isInteger=Me,jt.isLength=Ue,jt.isMap=function(t){return De(t)&&"[object Map]"==Fr(t)},jt.isMatch=function(t,n){return t===n||wn(t,n,Ur(n))},jt.isMatchWith=function(t,n,r){return r=typeof r=="function"?r:N,wn(t,n,Ur(n),r)},jt.isNaN=function(t){return Fe(t)&&t!=+t},jt.isNative=$e,jt.isNil=function(t){return null==t},jt.isNull=function(t){return null===t},jt.isNumber=Fe,jt.isObject=ze,jt.isObjectLike=De,jt.isPlainObject=Ne,jt.isRegExp=Pe,jt.isSafeInteger=function(t){return Me(t)&&t>=-9007199254740991&&9007199254740991>=t;
+},jt.isSet=function(t){return De(t)&&"[object Set]"==Fr(t)},jt.isString=Ze,jt.isSymbol=Te,jt.isTypedArray=qe,jt.isUndefined=function(t){return t===N},jt.isWeakMap=function(t){return De(t)&&"[object WeakMap]"==Fr(t)},jt.isWeakSet=function(t){return De(t)&&"[object WeakSet]"==ku.call(t)},jt.join=function(t,n){return t?qu.call(t,n):""},jt.kebabCase=Si,jt.last=ae,jt.lastIndexOf=function(t,n,r){var e=t?t.length:0;if(!e)return-1;var u=e;if(r!==N&&(u=Ke(r),u=(0>u?Ku(e+u,0):Gu(u,e-1))+1),n!==n)return B(t,u,true);
+for(;u--;)if(t[u]===n)return u;return-1},jt.lowerCase=Ri,jt.lowerFirst=Wi,jt.lt=hi,jt.lte=pi,jt.max=function(t){return t&&t.length?cn(t,au,gn):N},jt.maxBy=function(t,n){return t&&t.length?cn(t,Mr(n),gn):N},jt.mean=function(t){return y(t,au)},jt.meanBy=function(t,n){return y(t,Mr(n))},jt.min=function(t){return t&&t.length?cn(t,au,kn):N},jt.minBy=function(t,n){return t&&t.length?cn(t,Mr(n),kn):N},jt.multiply=Hi,jt.nth=function(t,n){return t&&t.length?Wn(t,Ke(n)):N},jt.noConflict=function(){return Vt._===this&&(Vt._=Eu),
+this},jt.noop=hu,jt.now=Xo,jt.pad=function(t,n,r){t=He(t);var e=(n=Ke(n))?D(t):0;return!n||e>=n?t:(n=(n-e)/2,Or(Pu(n),r)+t+Or(Nu(n),r))},jt.padEnd=function(t,n,r){t=He(t);var e=(n=Ke(n))?D(t):0;return n&&n>e?t+Or(n-e,r):t},jt.padStart=function(t,n,r){t=He(t);var e=(n=Ke(n))?D(t):0;return n&&n>e?Or(n-e,r)+t:t},jt.parseInt=function(t,n,r){return r||null==n?n=0:n&&(n=+n),t=He(t).replace(ot,""),Ju(t,n||(ht.test(t)?16:10))},jt.random=function(t,n,r){if(r&&typeof r!="boolean"&&Jr(t,n,r)&&(n=r=N),r===N&&(typeof n=="boolean"?(r=n,
+n=N):typeof t=="boolean"&&(r=t,t=N)),t===N&&n===N?(t=0,n=1):(t=Je(t)||0,n===N?(n=t,t=0):n=Je(n)||0),t>n){var e=t;t=n,n=e}return r||t%1||n%1?(r=Yu(),Gu(t+r*(n-t+Dt("1e-"+((r+"").length-1))),n)):$n(t,n)},jt.reduce=function(t,n,r){var e=li(t)?s:b,u=3>arguments.length;return e(t,Mr(n,4),r,u,yo)},jt.reduceRight=function(t,n,r){var e=li(t)?h:b,u=3>arguments.length;return e(t,Mr(n,4),r,u,bo)},jt.repeat=function(t,n,r){return n=(r?Jr(t,n,r):n===N)?1:Ke(n),Fn(He(t),n)},jt.replace=function(){var t=arguments,n=He(t[0]);
+return 3>t.length?n:Hu.call(n,t[1],t[2])},jt.result=function(t,n,r){n=Yr(n,t)?[n]:nr(n);var e=-1,u=n.length;for(u||(t=N,u=1);++e<u;){var o=null==t?N:t[ee(n[e])];o===N&&(e=u,o=r),t=Ce(o)?o.call(t):o}return t},jt.round=Qi,jt.runInContext=F,jt.sample=function(t){t=We(t)?t:uu(t);var n=t.length;return n>0?t[$n(0,n-1)]:N},jt.size=function(t){if(null==t)return 0;if(We(t)){var n=t.length;return n&&Ze(t)?D(t):n}return De(t)&&(n=Fr(t),"[object Map]"==n||"[object Set]"==n)?t.size:tu(t).length},jt.snakeCase=Bi,
+jt.some=function(t,n,r){var e=li(t)?p:Zn;return r&&Jr(t,n,r)&&(n=N),e(t,Mr(n,3))},jt.sortedIndex=function(t,n){return Tn(t,n)},jt.sortedIndexBy=function(t,n,r){return qn(t,n,Mr(r))},jt.sortedIndexOf=function(t,n){var r=t?t.length:0;if(r){var e=Tn(t,n);if(r>e&&Se(t[e],n))return e}return-1},jt.sortedLastIndex=function(t,n){return Tn(t,n,true)},jt.sortedLastIndexBy=function(t,n,r){return qn(t,n,Mr(r),true)},jt.sortedLastIndexOf=function(t,n){if(t&&t.length){var r=Tn(t,n,true)-1;if(Se(t[r],n))return r}return-1;
+},jt.startCase=Li,jt.startsWith=function(t,n,r){return t=He(t),r=tn(Ke(r),0,t.length),t.lastIndexOf(Gn(n),r)==r},jt.subtract=Xi,jt.sum=function(t){return t&&t.length?j(t,au):0},jt.sumBy=function(t,n){return t&&t.length?j(t,Mr(n)):0},jt.template=function(t,n,r){var e=jt.templateSettings;r&&Jr(t,n,r)&&(n=N),t=He(t),n=gi({},n,e,Gt),r=gi({},n.imports,e.imports,Gt);var u,o,i=tu(r),f=O(r,i),c=0;r=n.interpolate||bt;var a="__p+='";r=du((n.escape||bt).source+"|"+r.source+"|"+(r===X?lt:bt).source+"|"+(n.evaluate||bt).source+"|$","g");
+var l="sourceURL"in n?"//# sourceURL="+n.sourceURL+"\n":"";if(t.replace(r,function(n,r,e,i,f,l){return e||(e=i),a+=t.slice(c,l).replace(xt,W),r&&(u=true,a+="'+__e("+r+")+'"),f&&(o=true,a+="';"+f+";\n__p+='"),e&&(a+="'+((__t=("+e+"))==null?'':__t)+'"),c=l+n.length,n}),a+="';",(n=n.variable)||(a="with(obj){"+a+"}"),a=(o?a.replace(T,""):a).replace(q,"$1").replace(V,"$1;"),a="function("+(n||"obj")+"){"+(n?"":"obj||(obj={});")+"var __t,__p=''"+(u?",__e=_.escape":"")+(o?",__j=Array.prototype.join;function print(){__p+=__j.call(arguments,'')}":";")+a+"return __p}",
+n=Ui(function(){return Function(i,l+"return "+a).apply(N,f)}),n.source=a,Le(n))throw n;return n},jt.times=function(t,n){if(t=Ke(t),1>t||t>9007199254740991)return[];var r=4294967295,e=Gu(t,4294967295);for(n=Mr(n),t-=4294967295,e=m(e,n);++r<t;)n(r);return e},jt.toInteger=Ke,jt.toLength=Ge,jt.toLower=function(t){return He(t).toLowerCase()},jt.toNumber=Je,jt.toSafeInteger=function(t){return tn(Ke(t),-9007199254740991,9007199254740991)},jt.toString=He,jt.toUpper=function(t){return He(t).toUpperCase()},
+jt.trim=function(t,n,r){return(t=He(t))&&(r||n===N)?t.replace(ot,""):t&&(n=Gn(n))?(t=t.match(kt),n=n.match(kt),rr(t,k(t,n),E(t,n)+1).join("")):t},jt.trimEnd=function(t,n,r){return(t=He(t))&&(r||n===N)?t.replace(ft,""):t&&(n=Gn(n))?(t=t.match(kt),n=E(t,n.match(kt))+1,rr(t,0,n).join("")):t},jt.trimStart=function(t,n,r){return(t=He(t))&&(r||n===N)?t.replace(it,""):t&&(n=Gn(n))?(t=t.match(kt),n=k(t,n.match(kt)),rr(t,n).join("")):t},jt.truncate=function(t,n){var r=30,e="...";if(ze(n))var u="separator"in n?n.separator:u,r="length"in n?Ke(n.length):r,e="omission"in n?Gn(n.omission):e;
+t=He(t);var o=t.length;if(It.test(t))var i=t.match(kt),o=i.length;if(r>=o)return t;if(o=r-D(e),1>o)return e;if(r=i?rr(i,0,o).join(""):t.slice(0,o),u===N)return r+e;if(i&&(o+=r.length-o),Pe(u)){if(t.slice(o).search(u)){var f=r;for(u.global||(u=du(u.source,He(st.exec(u))+"g")),u.lastIndex=0;i=u.exec(f);)var c=i.index;r=r.slice(0,c===N?o:c)}}else t.indexOf(Gn(u),o)!=o&&(u=r.lastIndexOf(u),u>-1&&(r=r.slice(0,u)));return r+e},jt.unescape=function(t){return(t=He(t))&&J.test(t)?t.replace(K,$):t},jt.uniqueId=function(t){
+var n=++Au;return He(t)+n},jt.upperCase=Ci,jt.upperFirst=Mi,jt.each=de,jt.eachRight=ye,jt.first=ce,su(jt,function(){var t={};return sn(jt,function(n,r){wu.call(jt.prototype,r)||(t[r]=n)}),t}(),{chain:false}),jt.VERSION="4.11.2",u("bind bindKey curry curryRight partial partialRight".split(" "),function(t){jt[t].placeholder=jt}),u(["drop","take"],function(t,n){Lt.prototype[t]=function(r){var e=this.__filtered__;if(e&&!n)return new Lt(this);r=r===N?1:Ku(Ke(r),0);var u=this.clone();return e?u.__takeCount__=Gu(r,u.__takeCount__):u.__views__.push({
+size:Gu(r,4294967295),type:t+(0>u.__dir__?"Right":"")}),u},Lt.prototype[t+"Right"]=function(n){return this.reverse()[t](n).reverse()}}),u(["filter","map","takeWhile"],function(t,n){var r=n+1,e=1==r||3==r;Lt.prototype[t]=function(t){var n=this.clone();return n.__iteratees__.push({iteratee:Mr(t,3),type:r}),n.__filtered__=n.__filtered__||e,n}}),u(["head","last"],function(t,n){var r="take"+(n?"Right":"");Lt.prototype[t]=function(){return this[r](1).value()[0]}}),u(["initial","tail"],function(t,n){var r="drop"+(n?"":"Right");
+Lt.prototype[t]=function(){return this.__filtered__?new Lt(this):this[r](1)}}),Lt.prototype.compact=function(){return this.filter(au)},Lt.prototype.find=function(t){return this.filter(t).head()},Lt.prototype.findLast=function(t){return this.reverse().find(t)},Lt.prototype.invokeMap=Ee(function(t,n){return typeof t=="function"?new Lt(this):this.map(function(r){return jn(r,t,n)})}),Lt.prototype.reject=function(t){return t=Mr(t,3),this.filter(function(n){return!t(n)})},Lt.prototype.slice=function(t,n){
+t=Ke(t);var r=this;return r.__filtered__&&(t>0||0>n)?new Lt(r):(0>t?r=r.takeRight(-t):t&&(r=r.drop(t)),n!==N&&(n=Ke(n),r=0>n?r.dropRight(-n):r.take(n-t)),r)},Lt.prototype.takeRightWhile=function(t){return this.reverse().takeWhile(t).reverse()},Lt.prototype.toArray=function(){return this.take(4294967295)},sn(Lt.prototype,function(t,n){var r=/^(?:filter|find|map|reject)|While$/.test(n),e=/^(?:head|last)$/.test(n),u=jt[e?"take"+("last"==n?"Right":""):n],o=e||/^find/.test(n);u&&(jt.prototype[n]=function(){
+function n(t){return t=u.apply(jt,l([t],f)),e&&h?t[0]:t}var i=this.__wrapped__,f=e?[1]:arguments,c=i instanceof Lt,a=f[0],s=c||li(i);s&&r&&typeof a=="function"&&1!=a.length&&(c=s=false);var h=this.__chain__,p=!!this.__actions__.length,a=o&&!h,c=c&&!p;return!o&&s?(i=c?i:new Lt(this),i=t.apply(i,f),i.__actions__.push({func:ve,args:[n],thisArg:N}),new wt(i,h)):a&&c?t.apply(this,f):(i=this.thru(n),a?e?i.value()[0]:i.value():i)})}),u("pop push shift sort splice unshift".split(" "),function(t){var n=bu[t],r=/^(?:push|sort|unshift)$/.test(t)?"tap":"thru",e=/^(?:pop|shift)$/.test(t);
+jt.prototype[t]=function(){var t=arguments;if(e&&!this.__chain__){var u=this.value();return n.apply(li(u)?u:[],t)}return this[r](function(r){return n.apply(li(r)?r:[],t)})}}),sn(Lt.prototype,function(t,n){var r=jt[n];if(r){var e=r.name+"";(co[e]||(co[e]=[])).push({name:n,func:r})}}),co[jr(N,2).name]=[{name:"wrapper",func:N}],Lt.prototype.clone=function(){var t=new Lt(this.__wrapped__);return t.__actions__=cr(this.__actions__),t.__dir__=this.__dir__,t.__filtered__=this.__filtered__,t.__iteratees__=cr(this.__iteratees__),
+t.__takeCount__=this.__takeCount__,t.__views__=cr(this.__views__),t},Lt.prototype.reverse=function(){if(this.__filtered__){var t=new Lt(this);t.__dir__=-1,t.__filtered__=true}else t=this.clone(),t.__dir__*=-1;return t},Lt.prototype.value=function(){var t,n=this.__wrapped__.value(),r=this.__dir__,e=li(n),u=0>r,o=e?n.length:0;t=o;for(var i=this.__views__,f=0,c=-1,a=i.length;++c<a;){var l=i[c],s=l.size;switch(l.type){case"drop":f+=s;break;case"dropRight":t-=s;break;case"take":t=Gu(t,f+s);break;case"takeRight":
+f=Ku(f,t-s)}}if(t={start:f,end:t},i=t.start,f=t.end,t=f-i,u=u?f:i-1,i=this.__iteratees__,f=i.length,c=0,a=Gu(t,this.__takeCount__),!e||200>o||o==t&&a==t)return Hn(n,this.__actions__);e=[];t:for(;t--&&a>c;){for(u+=r,o=-1,l=n[u];++o<f;){var h=i[o],s=h.type,h=(0,h.iteratee)(l);if(2==s)l=h;else if(!h){if(1==s)continue t;break t}}e[c++]=l}return e},jt.prototype.at=Vo,jt.prototype.chain=function(){return _e(this)},jt.prototype.commit=function(){return new wt(this.value(),this.__chain__)},jt.prototype.next=function(){
+this.__values__===N&&(this.__values__=Ve(this.value()));var t=this.__index__>=this.__values__.length,n=t?N:this.__values__[this.__index__++];return{done:t,value:n}},jt.prototype.plant=function(t){for(var n,r=this;r instanceof mt;){var e=oe(r);e.__index__=0,e.__values__=N,n?u.__wrapped__=e:n=e;var u=e,r=r.__wrapped__}return u.__wrapped__=t,n},jt.prototype.reverse=function(){var t=this.__wrapped__;return t instanceof Lt?(this.__actions__.length&&(t=new Lt(this)),t=t.reverse(),t.__actions__.push({func:ve,
+args:[se],thisArg:N}),new wt(t,this.__chain__)):this.thru(se)},jt.prototype.toJSON=jt.prototype.valueOf=jt.prototype.value=function(){return Hn(this.__wrapped__,this.__actions__)},Uu&&(jt.prototype[Uu]=ge),jt}var N,P=1/0,Z=NaN,T=/\b__p\+='';/g,q=/\b(__p\+=)''\+/g,V=/(__e\(.*?\)|\b__t\))\+'';/g,K=/&(?:amp|lt|gt|quot|#39|#96);/g,G=/[&<>"'`]/g,J=RegExp(K.source),Y=RegExp(G.source),H=/<%-([\s\S]+?)%>/g,Q=/<%([\s\S]+?)%>/g,X=/<%=([\s\S]+?)%>/g,tt=/\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,nt=/^\w*$/,rt=/[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]/g,et=/[\\^$.*+?()[\]{}|]/g,ut=RegExp(et.source),ot=/^\s+|\s+$/g,it=/^\s+/,ft=/\s+$/,ct=/[a-zA-Z0-9]+/g,at=/\\(\\)?/g,lt=/\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g,st=/\w*$/,ht=/^0x/i,pt=/^[-+]0x[0-9a-f]+$/i,_t=/^0b[01]+$/i,vt=/^\[object .+?Constructor\]$/,gt=/^0o[0-7]+$/i,dt=/^(?:0|[1-9]\d*)$/,yt=/[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g,bt=/($^)/,xt=/['\n\r\u2028\u2029\\]/g,jt="[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|\\ud83c[\\udffb-\\udfff])?(?:\\u200d(?:[^\\ud800-\\udfff]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|\\ud83c[\\udffb-\\udfff])?)*",mt="(?:[\\u2700-\\u27bf]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff])"+jt,wt="(?:[^\\ud800-\\udfff][\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]?|[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|(?:\\ud83c[\\udde6-\\uddff]){2}|[\\ud800-\\udbff][\\udc00-\\udfff]|[\\ud800-\\udfff])",At=RegExp("['\u2019]","g"),Ot=RegExp("[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]","g"),kt=RegExp("\\ud83c[\\udffb-\\udfff](?=\\ud83c[\\udffb-\\udfff])|"+wt+jt,"g"),Et=RegExp(["[A-Z\\xc0-\\xd6\\xd8-\\xde]?[a-z\\xdf-\\xf6\\xf8-\\xff]+(?:['\u2019](?:d|ll|m|re|s|t|ve))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde]|$)|(?:[A-Z\\xc0-\\xd6\\xd8-\\xde]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?(?=[\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000]|[A-Z\\xc0-\\xd6\\xd8-\\xde](?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])|$)|[A-Z\\xc0-\\xd6\\xd8-\\xde]?(?:[a-z\\xdf-\\xf6\\xf8-\\xff]|[^\\ud800-\\udfff\\xac\\xb1\\xd7\\xf7\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf\\u2000-\\u206f \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000\\d+\\u2700-\\u27bfa-z\\xdf-\\xf6\\xf8-\\xffA-Z\\xc0-\\xd6\\xd8-\\xde])+(?:['\u2019](?:d|ll|m|re|s|t|ve))?|[A-Z\\xc0-\\xd6\\xd8-\\xde]+(?:['\u2019](?:D|LL|M|RE|S|T|VE))?|\\d+",mt].join("|"),"g"),It=RegExp("[\\u200d\\ud800-\\udfff\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0\\ufe0e\\ufe0f]"),St=/[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/,Rt="Array Buffer DataView Date Error Float32Array Float64Array Function Int8Array Int16Array Int32Array Map Math Object Promise Reflect RegExp Set String Symbol TypeError Uint8Array Uint8ClampedArray Uint16Array Uint32Array WeakMap _ clearTimeout isFinite parseInt setTimeout".split(" "),Wt={};
+Wt["[object Float32Array]"]=Wt["[object Float64Array]"]=Wt["[object Int8Array]"]=Wt["[object Int16Array]"]=Wt["[object Int32Array]"]=Wt["[object Uint8Array]"]=Wt["[object Uint8ClampedArray]"]=Wt["[object Uint16Array]"]=Wt["[object Uint32Array]"]=true,Wt["[object Arguments]"]=Wt["[object Array]"]=Wt["[object ArrayBuffer]"]=Wt["[object Boolean]"]=Wt["[object DataView]"]=Wt["[object Date]"]=Wt["[object Error]"]=Wt["[object Function]"]=Wt["[object Map]"]=Wt["[object Number]"]=Wt["[object Object]"]=Wt["[object RegExp]"]=Wt["[object Set]"]=Wt["[object String]"]=Wt["[object WeakMap]"]=false;
+var Bt={};Bt["[object Arguments]"]=Bt["[object Array]"]=Bt["[object ArrayBuffer]"]=Bt["[object DataView]"]=Bt["[object Boolean]"]=Bt["[object Date]"]=Bt["[object Float32Array]"]=Bt["[object Float64Array]"]=Bt["[object Int8Array]"]=Bt["[object Int16Array]"]=Bt["[object Int32Array]"]=Bt["[object Map]"]=Bt["[object Number]"]=Bt["[object Object]"]=Bt["[object RegExp]"]=Bt["[object Set]"]=Bt["[object String]"]=Bt["[object Symbol]"]=Bt["[object Uint8Array]"]=Bt["[object Uint8ClampedArray]"]=Bt["[object Uint16Array]"]=Bt["[object Uint32Array]"]=true,
+Bt["[object Error]"]=Bt["[object Function]"]=Bt["[object WeakMap]"]=false;var Lt={"\xc0":"A","\xc1":"A","\xc2":"A","\xc3":"A","\xc4":"A","\xc5":"A","\xe0":"a","\xe1":"a","\xe2":"a","\xe3":"a","\xe4":"a","\xe5":"a","\xc7":"C","\xe7":"c","\xd0":"D","\xf0":"d","\xc8":"E","\xc9":"E","\xca":"E","\xcb":"E","\xe8":"e","\xe9":"e","\xea":"e","\xeb":"e","\xcc":"I","\xcd":"I","\xce":"I","\xcf":"I","\xec":"i","\xed":"i","\xee":"i","\xef":"i","\xd1":"N","\xf1":"n","\xd2":"O","\xd3":"O","\xd4":"O","\xd5":"O","\xd6":"O",
+"\xd8":"O","\xf2":"o","\xf3":"o","\xf4":"o","\xf5":"o","\xf6":"o","\xf8":"o","\xd9":"U","\xda":"U","\xdb":"U","\xdc":"U","\xf9":"u","\xfa":"u","\xfb":"u","\xfc":"u","\xdd":"Y","\xfd":"y","\xff":"y","\xc6":"Ae","\xe6":"ae","\xde":"Th","\xfe":"th","\xdf":"ss"},Ct={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;","`":"&#96;"},Mt={"&amp;":"&","&lt;":"<","&gt;":">","&quot;":'"',"&#39;":"'","&#96;":"`"},Ut={"function":true,object:true},zt={"\\":"\\","'":"'","\n":"n","\r":"r","\u2028":"u2028","\u2029":"u2029"
+},Dt=parseFloat,$t=parseInt,Ft=Ut[typeof exports]&&exports&&!exports.nodeType?exports:N,Nt=Ut[typeof module]&&module&&!module.nodeType?module:N,Pt=Nt&&Nt.exports===Ft?Ft:N,Zt=I(Ut[typeof self]&&self),Tt=I(Ut[typeof window]&&window),qt=I(Ut[typeof this]&&this),Vt=I(Ft&&Nt&&typeof global=="object"&&global)||Tt!==(qt&&qt.window)&&Tt||Zt||qt||Function("return this")(),Kt=F();(Tt||Zt||{})._=Kt,typeof define=="function"&&typeof define.amd=="object"&&define.amd? define(function(){return Kt}):Ft&&Nt?(Pt&&((Nt.exports=Kt)._=Kt),
+Ft._=Kt):Vt._=Kt}).call(this);
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/mapping.fp.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/mapping.fp.js
new file mode 100644
index 0000000..8adef99
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/dist/mapping.fp.js
@@ -0,0 +1,352 @@
+(function webpackUniversalModuleDefinition(root, factory) {
+	if(typeof exports === 'object' && typeof module === 'object')
+		module.exports = factory();
+	else if(typeof define === 'function' && define.amd)
+		define([], factory);
+	else if(typeof exports === 'object')
+		exports["mapping"] = factory();
+	else
+		root["mapping"] = factory();
+})(this, function() {
+return /******/ (function(modules) { // webpackBootstrap
+/******/ 	// The module cache
+/******/ 	var installedModules = {};
+
+/******/ 	// The require function
+/******/ 	function __webpack_require__(moduleId) {
+
+/******/ 		// Check if module is in cache
+/******/ 		if(installedModules[moduleId])
+/******/ 			return installedModules[moduleId].exports;
+
+/******/ 		// Create a new module (and put it into the cache)
+/******/ 		var module = installedModules[moduleId] = {
+/******/ 			exports: {},
+/******/ 			id: moduleId,
+/******/ 			loaded: false
+/******/ 		};
+
+/******/ 		// Execute the module function
+/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
+
+/******/ 		// Flag the module as loaded
+/******/ 		module.loaded = true;
+
+/******/ 		// Return the exports of the module
+/******/ 		return module.exports;
+/******/ 	}
+
+
+/******/ 	// expose the modules object (__webpack_modules__)
+/******/ 	__webpack_require__.m = modules;
+
+/******/ 	// expose the module cache
+/******/ 	__webpack_require__.c = installedModules;
+
+/******/ 	// __webpack_public_path__
+/******/ 	__webpack_require__.p = "";
+
+/******/ 	// Load entry module and return exports
+/******/ 	return __webpack_require__(0);
+/******/ })
+/************************************************************************/
+/******/ ([
+/* 0 */
+/***/ function(module, exports) {
+
+	/** Used to map aliases to their real names. */
+	exports.aliasToReal = {
+
+	  // Lodash aliases.
+	  'each': 'forEach',
+	  'eachRight': 'forEachRight',
+	  'entries': 'toPairs',
+	  'entriesIn': 'toPairsIn',
+	  'extend': 'assignIn',
+	  'extendWith': 'assignInWith',
+	  'first': 'head',
+
+	  // Ramda aliases.
+	  '__': 'placeholder',
+	  'all': 'every',
+	  'allPass': 'overEvery',
+	  'always': 'constant',
+	  'any': 'some',
+	  'anyPass': 'overSome',
+	  'apply': 'spread',
+	  'assoc': 'set',
+	  'assocPath': 'set',
+	  'complement': 'negate',
+	  'compose': 'flowRight',
+	  'contains': 'includes',
+	  'dissoc': 'unset',
+	  'dissocPath': 'unset',
+	  'equals': 'isEqual',
+	  'identical': 'eq',
+	  'init': 'initial',
+	  'invertObj': 'invert',
+	  'juxt': 'over',
+	  'omitAll': 'omit',
+	  'nAry': 'ary',
+	  'path': 'get',
+	  'pathEq': 'matchesProperty',
+	  'pathOr': 'getOr',
+	  'paths': 'at',
+	  'pickAll': 'pick',
+	  'pipe': 'flow',
+	  'pluck': 'map',
+	  'prop': 'get',
+	  'propEq': 'matchesProperty',
+	  'propOr': 'getOr',
+	  'props': 'at',
+	  'unapply': 'rest',
+	  'unnest': 'flatten',
+	  'useWith': 'overArgs',
+	  'whereEq': 'filter',
+	  'zipObj': 'zipObject'
+	};
+
+	/** Used to map ary to method names. */
+	exports.aryMethod = {
+	  '1': [
+	    'attempt', 'castArray', 'ceil', 'create', 'curry', 'curryRight', 'floor',
+	    'flow', 'flowRight', 'fromPairs', 'invert', 'iteratee', 'memoize', 'method',
+	    'methodOf', 'mixin', 'over', 'overEvery', 'overSome', 'rest', 'reverse',
+	    'round', 'runInContext', 'spread', 'template', 'trim', 'trimEnd', 'trimStart',
+	    'uniqueId', 'words'
+	  ],
+	  '2': [
+	    'add', 'after', 'ary', 'assign', 'assignIn', 'at', 'before', 'bind', 'bindAll',
+	    'bindKey', 'chunk', 'cloneDeepWith', 'cloneWith', 'concat', 'countBy', 'curryN',
+	    'curryRightN', 'debounce', 'defaults', 'defaultsDeep', 'delay', 'difference',
+	    'divide', 'drop', 'dropRight', 'dropRightWhile', 'dropWhile', 'endsWith',
+	    'eq', 'every', 'filter', 'find', 'find', 'findIndex', 'findKey', 'findLast',
+	    'findLastIndex', 'findLastKey', 'flatMap', 'flatMapDeep', 'flattenDepth',
+	    'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight',
+	    'get', 'groupBy', 'gt', 'gte', 'has', 'hasIn', 'includes', 'indexOf',
+	    'intersection', 'invertBy', 'invoke', 'invokeMap', 'isEqual', 'isMatch',
+	    'join', 'keyBy', 'lastIndexOf', 'lt', 'lte', 'map', 'mapKeys', 'mapValues',
+	    'matchesProperty', 'maxBy', 'meanBy', 'merge', 'minBy', 'multiply', 'nth',
+	    'omit', 'omitBy', 'overArgs', 'pad', 'padEnd', 'padStart', 'parseInt',
+	    'partial', 'partialRight', 'partition', 'pick', 'pickBy', 'pull', 'pullAll',
+	    'pullAt', 'random', 'range', 'rangeRight', 'rearg', 'reject', 'remove',
+	    'repeat', 'restFrom', 'result', 'sampleSize', 'some', 'sortBy', 'sortedIndex',
+	    'sortedIndexOf', 'sortedLastIndex', 'sortedLastIndexOf', 'sortedUniqBy',
+	    'split', 'spreadFrom', 'startsWith', 'subtract', 'sumBy', 'take', 'takeRight',
+	    'takeRightWhile', 'takeWhile', 'tap', 'throttle', 'thru', 'times', 'trimChars',
+	    'trimCharsEnd', 'trimCharsStart', 'truncate', 'union', 'uniqBy', 'uniqWith',
+	    'unset', 'unzipWith', 'without', 'wrap', 'xor', 'zip', 'zipObject',
+	    'zipObjectDeep'
+	  ],
+	  '3': [
+	    'assignInWith', 'assignWith', 'clamp', 'differenceBy', 'differenceWith',
+	    'getOr', 'inRange', 'intersectionBy', 'intersectionWith', 'invokeArgs',
+	    'invokeArgsMap', 'isEqualWith', 'isMatchWith', 'flatMapDepth', 'mergeWith',
+	    'orderBy', 'padChars', 'padCharsEnd', 'padCharsStart', 'pullAllBy',
+	    'pullAllWith', 'reduce', 'reduceRight', 'replace', 'set', 'slice',
+	    'sortedIndexBy', 'sortedLastIndexBy', 'transform', 'unionBy', 'unionWith',
+	    'update', 'xorBy', 'xorWith', 'zipWith'
+	  ],
+	  '4': [
+	    'fill', 'setWith', 'updateWith'
+	  ]
+	};
+
+	/** Used to map ary to rearg configs. */
+	exports.aryRearg = {
+	  '2': [1, 0],
+	  '3': [2, 0, 1],
+	  '4': [3, 2, 0, 1]
+	};
+
+	/** Used to map method names to their iteratee ary. */
+	exports.iterateeAry = {
+	  'dropRightWhile': 1,
+	  'dropWhile': 1,
+	  'every': 1,
+	  'filter': 1,
+	  'find': 1,
+	  'findIndex': 1,
+	  'findKey': 1,
+	  'findLast': 1,
+	  'findLastIndex': 1,
+	  'findLastKey': 1,
+	  'flatMap': 1,
+	  'flatMapDeep': 1,
+	  'flatMapDepth': 1,
+	  'forEach': 1,
+	  'forEachRight': 1,
+	  'forIn': 1,
+	  'forInRight': 1,
+	  'forOwn': 1,
+	  'forOwnRight': 1,
+	  'map': 1,
+	  'mapKeys': 1,
+	  'mapValues': 1,
+	  'partition': 1,
+	  'reduce': 2,
+	  'reduceRight': 2,
+	  'reject': 1,
+	  'remove': 1,
+	  'some': 1,
+	  'takeRightWhile': 1,
+	  'takeWhile': 1,
+	  'times': 1,
+	  'transform': 2
+	};
+
+	/** Used to map method names to iteratee rearg configs. */
+	exports.iterateeRearg = {
+	  'mapKeys': [1]
+	};
+
+	/** Used to map method names to rearg configs. */
+	exports.methodRearg = {
+	  'assignInWith': [1, 2, 0],
+	  'assignWith': [1, 2, 0],
+	  'getOr': [2, 1, 0],
+	  'isEqualWith': [1, 2, 0],
+	  'isMatchWith': [2, 1, 0],
+	  'mergeWith': [1, 2, 0],
+	  'padChars': [2, 1, 0],
+	  'padCharsEnd': [2, 1, 0],
+	  'padCharsStart': [2, 1, 0],
+	  'pullAllBy': [2, 1, 0],
+	  'pullAllWith': [2, 1, 0],
+	  'setWith': [3, 1, 2, 0],
+	  'sortedIndexBy': [2, 1, 0],
+	  'sortedLastIndexBy': [2, 1, 0],
+	  'updateWith': [3, 1, 2, 0],
+	  'zipWith': [1, 2, 0]
+	};
+
+	/** Used to map method names to spread configs. */
+	exports.methodSpread = {
+	  'invokeArgs': 2,
+	  'invokeArgsMap': 2,
+	  'partial': 1,
+	  'partialRight': 1,
+	  'without': 1
+	};
+
+	/** Used to identify methods which mutate arrays or objects. */
+	exports.mutate = {
+	  'array': {
+	    'fill': true,
+	    'pull': true,
+	    'pullAll': true,
+	    'pullAllBy': true,
+	    'pullAllWith': true,
+	    'pullAt': true,
+	    'remove': true,
+	    'reverse': true
+	  },
+	  'object': {
+	    'assign': true,
+	    'assignIn': true,
+	    'assignInWith': true,
+	    'assignWith': true,
+	    'defaults': true,
+	    'defaultsDeep': true,
+	    'merge': true,
+	    'mergeWith': true
+	  },
+	  'set': {
+	    'set': true,
+	    'setWith': true,
+	    'unset': true,
+	    'update': true,
+	    'updateWith': true
+	  }
+	};
+
+	/** Used to track methods with placeholder support */
+	exports.placeholder = {
+	  'bind': true,
+	  'bindKey': true,
+	  'curry': true,
+	  'curryRight': true,
+	  'partial': true,
+	  'partialRight': true
+	};
+
+	/** Used to map real names to their aliases. */
+	exports.realToAlias = (function() {
+	  var hasOwnProperty = Object.prototype.hasOwnProperty,
+	      object = exports.aliasToReal,
+	      result = {};
+
+	  for (var key in object) {
+	    var value = object[key];
+	    if (hasOwnProperty.call(result, value)) {
+	      result[value].push(key);
+	    } else {
+	      result[value] = [key];
+	    }
+	  }
+	  return result;
+	}());
+
+	/** Used to map method names to other names. */
+	exports.remap = {
+	  'curryN': 'curry',
+	  'curryRightN': 'curryRight',
+	  'getOr': 'get',
+	  'invokeArgs': 'invoke',
+	  'invokeArgsMap': 'invokeMap',
+	  'padChars': 'pad',
+	  'padCharsEnd': 'padEnd',
+	  'padCharsStart': 'padStart',
+	  'restFrom': 'rest',
+	  'spreadFrom': 'spread',
+	  'trimChars': 'trim',
+	  'trimCharsEnd': 'trimEnd',
+	  'trimCharsStart': 'trimStart'
+	};
+
+	/** Used to track methods that skip fixing their arity. */
+	exports.skipFixed = {
+	  'castArray': true,
+	  'flow': true,
+	  'flowRight': true,
+	  'iteratee': true,
+	  'mixin': true,
+	  'runInContext': true
+	};
+
+	/** Used to track methods that skip rearranging arguments. */
+	exports.skipRearg = {
+	  'add': true,
+	  'assign': true,
+	  'assignIn': true,
+	  'bind': true,
+	  'bindKey': true,
+	  'concat': true,
+	  'difference': true,
+	  'divide': true,
+	  'eq': true,
+	  'gt': true,
+	  'gte': true,
+	  'isEqual': true,
+	  'lt': true,
+	  'lte': true,
+	  'matchesProperty': true,
+	  'merge': true,
+	  'multiply': true,
+	  'overArgs': true,
+	  'partial': true,
+	  'partialRight': true,
+	  'random': true,
+	  'range': true,
+	  'rangeRight': true,
+	  'subtract': true,
+	  'without': true,
+	  'zip': true,
+	  'zipObject': true
+	};
+
+
+/***/ }
+/******/ ])
+});
+;
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/doc/README.md b/views/ngXosViews/serviceGrid/src/vendor/lodash/doc/README.md
new file mode 100644
index 0000000..f9da2b3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/doc/README.md
@@ -0,0 +1,10738 @@
+# <a href="https://lodash.com/">lodash</a> <span>v4.11.2</span>
+
+<!-- div class="toc-container" -->
+
+<!-- div -->
+
+## `Array`
+* <a href="#_chunkarray-size1">`_.chunk`</a>
+* <a href="#_compactarray">`_.compact`</a>
+* <a href="#_concatarray-values">`_.concat`</a>
+* <a href="#_differencearray-values">`_.difference`</a>
+* <a href="#_differencebyarray-values-iteratee_identity">`_.differenceBy`</a>
+* <a href="#_differencewitharray-values-comparator">`_.differenceWith`</a>
+* <a href="#_droparray-n1">`_.drop`</a>
+* <a href="#_droprightarray-n1">`_.dropRight`</a>
+* <a href="#_droprightwhilearray-predicate_identity">`_.dropRightWhile`</a>
+* <a href="#_dropwhilearray-predicate_identity">`_.dropWhile`</a>
+* <a href="#_fillarray-value-start0-endarraylength">`_.fill`</a>
+* <a href="#_findindexarray-predicate_identity">`_.findIndex`</a>
+* <a href="#_findlastindexarray-predicate_identity">`_.findLastIndex`</a>
+* <a href="#_headarray" class="alias">`_.first` -> `head`</a>
+* <a href="#_flattenarray">`_.flatten`</a>
+* <a href="#_flattendeeparray">`_.flattenDeep`</a>
+* <a href="#_flattendeptharray-depth1">`_.flattenDepth`</a>
+* <a href="#_frompairspairs">`_.fromPairs`</a>
+* <a href="#_headarray">`_.head`</a>
+* <a href="#_indexofarray-value-fromindex0">`_.indexOf`</a>
+* <a href="#_initialarray">`_.initial`</a>
+* <a href="#_intersectionarrays">`_.intersection`</a>
+* <a href="#_intersectionbyarrays-iteratee_identity">`_.intersectionBy`</a>
+* <a href="#_intersectionwitharrays-comparator">`_.intersectionWith`</a>
+* <a href="#_joinarray-separator-">`_.join`</a>
+* <a href="#_lastarray">`_.last`</a>
+* <a href="#_lastindexofarray-value-fromindexarraylength-1">`_.lastIndexOf`</a>
+* <a href="#_ntharray-n0">`_.nth`</a>
+* <a href="#_pullarray-values">`_.pull`</a>
+* <a href="#_pullallarray-values">`_.pullAll`</a>
+* <a href="#_pullallbyarray-values-iteratee_identity">`_.pullAllBy`</a>
+* <a href="#_pullallwitharray-values-comparator">`_.pullAllWith`</a>
+* <a href="#_pullatarray-indexes">`_.pullAt`</a>
+* <a href="#_removearray-predicate_identity">`_.remove`</a>
+* <a href="#_reversearray">`_.reverse`</a>
+* <a href="#_slicearray-start0-endarraylength">`_.slice`</a>
+* <a href="#_sortedindexarray-value">`_.sortedIndex`</a>
+* <a href="#_sortedindexbyarray-value-iteratee_identity">`_.sortedIndexBy`</a>
+* <a href="#_sortedindexofarray-value">`_.sortedIndexOf`</a>
+* <a href="#_sortedlastindexarray-value">`_.sortedLastIndex`</a>
+* <a href="#_sortedlastindexbyarray-value-iteratee_identity">`_.sortedLastIndexBy`</a>
+* <a href="#_sortedlastindexofarray-value">`_.sortedLastIndexOf`</a>
+* <a href="#_sorteduniqarray">`_.sortedUniq`</a>
+* <a href="#_sorteduniqbyarray-iteratee">`_.sortedUniqBy`</a>
+* <a href="#_tailarray">`_.tail`</a>
+* <a href="#_takearray-n1">`_.take`</a>
+* <a href="#_takerightarray-n1">`_.takeRight`</a>
+* <a href="#_takerightwhilearray-predicate_identity">`_.takeRightWhile`</a>
+* <a href="#_takewhilearray-predicate_identity">`_.takeWhile`</a>
+* <a href="#_unionarrays">`_.union`</a>
+* <a href="#_unionbyarrays-iteratee_identity">`_.unionBy`</a>
+* <a href="#_unionwitharrays-comparator">`_.unionWith`</a>
+* <a href="#_uniqarray">`_.uniq`</a>
+* <a href="#_uniqbyarray-iteratee_identity">`_.uniqBy`</a>
+* <a href="#_uniqwitharray-comparator">`_.uniqWith`</a>
+* <a href="#_unziparray">`_.unzip`</a>
+* <a href="#_unzipwitharray-iteratee_identity">`_.unzipWith`</a>
+* <a href="#_withoutarray-values">`_.without`</a>
+* <a href="#_xorarrays">`_.xor`</a>
+* <a href="#_xorbyarrays-iteratee_identity">`_.xorBy`</a>
+* <a href="#_xorwitharrays-comparator">`_.xorWith`</a>
+* <a href="#_ziparrays">`_.zip`</a>
+* <a href="#_zipobjectprops-values">`_.zipObject`</a>
+* <a href="#_zipobjectdeepprops-values">`_.zipObjectDeep`</a>
+* <a href="#_zipwitharrays-iteratee_identity">`_.zipWith`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Collection`
+* <a href="#_countbycollection-iteratee_identity">`_.countBy`</a>
+* <a href="#_foreachcollection-iteratee_identity" class="alias">`_.each` -> `forEach`</a>
+* <a href="#_foreachrightcollection-iteratee_identity" class="alias">`_.eachRight` -> `forEachRight`</a>
+* <a href="#_everycollection-predicate_identity">`_.every`</a>
+* <a href="#_filtercollection-predicate_identity">`_.filter`</a>
+* <a href="#_findcollection-predicate_identity">`_.find`</a>
+* <a href="#_findlastcollection-predicate_identity">`_.findLast`</a>
+* <a href="#_flatmapcollection-iteratee_identity">`_.flatMap`</a>
+* <a href="#_flatmapdeepcollection-iteratee_identity">`_.flatMapDeep`</a>
+* <a href="#_flatmapdepthcollection-iteratee_identity-depth1">`_.flatMapDepth`</a>
+* <a href="#_foreachcollection-iteratee_identity">`_.forEach`</a>
+* <a href="#_foreachrightcollection-iteratee_identity">`_.forEachRight`</a>
+* <a href="#_groupbycollection-iteratee_identity">`_.groupBy`</a>
+* <a href="#_includescollection-value-fromindex0">`_.includes`</a>
+* <a href="#_invokemapcollection-path-args">`_.invokeMap`</a>
+* <a href="#_keybycollection-iteratee_identity">`_.keyBy`</a>
+* <a href="#_mapcollection-iteratee_identity">`_.map`</a>
+* <a href="#_orderbycollection-iteratees_identity-orders">`_.orderBy`</a>
+* <a href="#_partitioncollection-predicate_identity">`_.partition`</a>
+* <a href="#_reducecollection-iteratee_identity-accumulator">`_.reduce`</a>
+* <a href="#_reducerightcollection-iteratee_identity-accumulator">`_.reduceRight`</a>
+* <a href="#_rejectcollection-predicate_identity">`_.reject`</a>
+* <a href="#_samplecollection">`_.sample`</a>
+* <a href="#_samplesizecollection-n1">`_.sampleSize`</a>
+* <a href="#_shufflecollection">`_.shuffle`</a>
+* <a href="#_sizecollection">`_.size`</a>
+* <a href="#_somecollection-predicate_identity">`_.some`</a>
+* <a href="#_sortbycollection-iteratees_identity">`_.sortBy`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Date`
+* <a href="#_now">`_.now`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Function`
+* <a href="#_aftern-func">`_.after`</a>
+* <a href="#_aryfunc-nfunclength">`_.ary`</a>
+* <a href="#_beforen-func">`_.before`</a>
+* <a href="#_bindfunc-thisarg-partials">`_.bind`</a>
+* <a href="#_bindkeyobject-key-partials">`_.bindKey`</a>
+* <a href="#_curryfunc-arityfunclength">`_.curry`</a>
+* <a href="#_curryrightfunc-arityfunclength">`_.curryRight`</a>
+* <a href="#_debouncefunc-wait0-options-optionsleadingfalse-optionsmaxwait-optionstrailingtrue">`_.debounce`</a>
+* <a href="#_deferfunc-args">`_.defer`</a>
+* <a href="#_delayfunc-wait-args">`_.delay`</a>
+* <a href="#_flipfunc">`_.flip`</a>
+* <a href="#_memoizefunc-resolver">`_.memoize`</a>
+* <a href="#_negatepredicate">`_.negate`</a>
+* <a href="#_oncefunc">`_.once`</a>
+* <a href="#_overargsfunc">`_.overArgs`</a>
+* <a href="#_partialfunc-partials">`_.partial`</a>
+* <a href="#_partialrightfunc-partials">`_.partialRight`</a>
+* <a href="#_reargfunc-indexes">`_.rearg`</a>
+* <a href="#_restfunc-startfunclength-1">`_.rest`</a>
+* <a href="#_spreadfunc-start0">`_.spread`</a>
+* <a href="#_throttlefunc-wait0-options-optionsleadingtrue-optionstrailingtrue">`_.throttle`</a>
+* <a href="#_unaryfunc">`_.unary`</a>
+* <a href="#_wrapvalue-wrapperidentity">`_.wrap`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Lang`
+* <a href="#_castarrayvalue">`_.castArray`</a>
+* <a href="#_clonevalue">`_.clone`</a>
+* <a href="#_clonedeepvalue">`_.cloneDeep`</a>
+* <a href="#_clonedeepwithvalue-customizer">`_.cloneDeepWith`</a>
+* <a href="#_clonewithvalue-customizer">`_.cloneWith`</a>
+* <a href="#_eqvalue-other">`_.eq`</a>
+* <a href="#_gtvalue-other">`_.gt`</a>
+* <a href="#_gtevalue-other">`_.gte`</a>
+* <a href="#_isargumentsvalue">`_.isArguments`</a>
+* <a href="#_isarrayvalue">`_.isArray`</a>
+* <a href="#_isarraybuffervalue">`_.isArrayBuffer`</a>
+* <a href="#_isarraylikevalue">`_.isArrayLike`</a>
+* <a href="#_isarraylikeobjectvalue">`_.isArrayLikeObject`</a>
+* <a href="#_isbooleanvalue">`_.isBoolean`</a>
+* <a href="#_isbuffervalue">`_.isBuffer`</a>
+* <a href="#_isdatevalue">`_.isDate`</a>
+* <a href="#_iselementvalue">`_.isElement`</a>
+* <a href="#_isemptyvalue">`_.isEmpty`</a>
+* <a href="#_isequalvalue-other">`_.isEqual`</a>
+* <a href="#_isequalwithvalue-other-customizer">`_.isEqualWith`</a>
+* <a href="#_iserrorvalue">`_.isError`</a>
+* <a href="#_isfinitevalue">`_.isFinite`</a>
+* <a href="#_isfunctionvalue">`_.isFunction`</a>
+* <a href="#_isintegervalue">`_.isInteger`</a>
+* <a href="#_islengthvalue">`_.isLength`</a>
+* <a href="#_ismapvalue">`_.isMap`</a>
+* <a href="#_ismatchobject-source">`_.isMatch`</a>
+* <a href="#_ismatchwithobject-source-customizer">`_.isMatchWith`</a>
+* <a href="#_isnanvalue">`_.isNaN`</a>
+* <a href="#_isnativevalue">`_.isNative`</a>
+* <a href="#_isnilvalue">`_.isNil`</a>
+* <a href="#_isnullvalue">`_.isNull`</a>
+* <a href="#_isnumbervalue">`_.isNumber`</a>
+* <a href="#_isobjectvalue">`_.isObject`</a>
+* <a href="#_isobjectlikevalue">`_.isObjectLike`</a>
+* <a href="#_isplainobjectvalue">`_.isPlainObject`</a>
+* <a href="#_isregexpvalue">`_.isRegExp`</a>
+* <a href="#_issafeintegervalue">`_.isSafeInteger`</a>
+* <a href="#_issetvalue">`_.isSet`</a>
+* <a href="#_isstringvalue">`_.isString`</a>
+* <a href="#_issymbolvalue">`_.isSymbol`</a>
+* <a href="#_istypedarrayvalue">`_.isTypedArray`</a>
+* <a href="#_isundefinedvalue">`_.isUndefined`</a>
+* <a href="#_isweakmapvalue">`_.isWeakMap`</a>
+* <a href="#_isweaksetvalue">`_.isWeakSet`</a>
+* <a href="#_ltvalue-other">`_.lt`</a>
+* <a href="#_ltevalue-other">`_.lte`</a>
+* <a href="#_toarrayvalue">`_.toArray`</a>
+* <a href="#_tointegervalue">`_.toInteger`</a>
+* <a href="#_tolengthvalue">`_.toLength`</a>
+* <a href="#_tonumbervalue">`_.toNumber`</a>
+* <a href="#_toplainobjectvalue">`_.toPlainObject`</a>
+* <a href="#_tosafeintegervalue">`_.toSafeInteger`</a>
+* <a href="#_tostringvalue">`_.toString`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Math`
+* <a href="#_addaugend-addend">`_.add`</a>
+* <a href="#_ceilnumber-precision0">`_.ceil`</a>
+* <a href="#_dividedividend-divisor">`_.divide`</a>
+* <a href="#_floornumber-precision0">`_.floor`</a>
+* <a href="#_maxarray">`_.max`</a>
+* <a href="#_maxbyarray-iteratee_identity">`_.maxBy`</a>
+* <a href="#_meanarray">`_.mean`</a>
+* <a href="#_meanbyarray-iteratee_identity">`_.meanBy`</a>
+* <a href="#_minarray">`_.min`</a>
+* <a href="#_minbyarray-iteratee_identity">`_.minBy`</a>
+* <a href="#_multiplymultiplier-multiplicand">`_.multiply`</a>
+* <a href="#_roundnumber-precision0">`_.round`</a>
+* <a href="#_subtractminuend-subtrahend">`_.subtract`</a>
+* <a href="#_sumarray">`_.sum`</a>
+* <a href="#_sumbyarray-iteratee_identity">`_.sumBy`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Number`
+* <a href="#_clampnumber-lower-upper">`_.clamp`</a>
+* <a href="#_inrangenumber-start0-end">`_.inRange`</a>
+* <a href="#_randomlower0-upper1-floating">`_.random`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Object`
+* <a href="#_assignobject-sources">`_.assign`</a>
+* <a href="#_assigninobject-sources">`_.assignIn`</a>
+* <a href="#_assigninwithobject-sources-customizer">`_.assignInWith`</a>
+* <a href="#_assignwithobject-sources-customizer">`_.assignWith`</a>
+* <a href="#_atobject-paths">`_.at`</a>
+* <a href="#_createprototype-properties">`_.create`</a>
+* <a href="#_defaultsobject-sources">`_.defaults`</a>
+* <a href="#_defaultsdeepobject-sources">`_.defaultsDeep`</a>
+* <a href="#_topairsobject" class="alias">`_.entries` -> `toPairs`</a>
+* <a href="#_topairsinobject" class="alias">`_.entriesIn` -> `toPairsIn`</a>
+* <a href="#_assigninobject-sources" class="alias">`_.extend` -> `assignIn`</a>
+* <a href="#_assigninwithobject-sources-customizer" class="alias">`_.extendWith` -> `assignInWith`</a>
+* <a href="#_findkeyobject-predicate_identity">`_.findKey`</a>
+* <a href="#_findlastkeyobject-predicate_identity">`_.findLastKey`</a>
+* <a href="#_forinobject-iteratee_identity">`_.forIn`</a>
+* <a href="#_forinrightobject-iteratee_identity">`_.forInRight`</a>
+* <a href="#_forownobject-iteratee_identity">`_.forOwn`</a>
+* <a href="#_forownrightobject-iteratee_identity">`_.forOwnRight`</a>
+* <a href="#_functionsobject">`_.functions`</a>
+* <a href="#_functionsinobject">`_.functionsIn`</a>
+* <a href="#_getobject-path-defaultvalue">`_.get`</a>
+* <a href="#_hasobject-path">`_.has`</a>
+* <a href="#_hasinobject-path">`_.hasIn`</a>
+* <a href="#_invertobject">`_.invert`</a>
+* <a href="#_invertbyobject-iteratee_identity">`_.invertBy`</a>
+* <a href="#_invokeobject-path-args">`_.invoke`</a>
+* <a href="#_keysobject">`_.keys`</a>
+* <a href="#_keysinobject">`_.keysIn`</a>
+* <a href="#_mapkeysobject-iteratee_identity">`_.mapKeys`</a>
+* <a href="#_mapvaluesobject-iteratee_identity">`_.mapValues`</a>
+* <a href="#_mergeobject-sources">`_.merge`</a>
+* <a href="#_mergewithobject-sources-customizer">`_.mergeWith`</a>
+* <a href="#_omitobject-props">`_.omit`</a>
+* <a href="#_omitbyobject-predicate_identity">`_.omitBy`</a>
+* <a href="#_pickobject-props">`_.pick`</a>
+* <a href="#_pickbyobject-predicate_identity">`_.pickBy`</a>
+* <a href="#_resultobject-path-defaultvalue">`_.result`</a>
+* <a href="#_setobject-path-value">`_.set`</a>
+* <a href="#_setwithobject-path-value-customizer">`_.setWith`</a>
+* <a href="#_topairsobject">`_.toPairs`</a>
+* <a href="#_topairsinobject">`_.toPairsIn`</a>
+* <a href="#_transformobject-iteratee_identity-accumulator">`_.transform`</a>
+* <a href="#_unsetobject-path">`_.unset`</a>
+* <a href="#_updateobject-path-updater">`_.update`</a>
+* <a href="#_updatewithobject-path-updater-customizer">`_.updateWith`</a>
+* <a href="#_valuesobject">`_.values`</a>
+* <a href="#_valuesinobject">`_.valuesIn`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Seq`
+* <a href="#_value">`_`</a>
+* <a href="#_chainvalue">`_.chain`</a>
+* <a href="#_tapvalue-interceptor">`_.tap`</a>
+* <a href="#_thruvalue-interceptor">`_.thru`</a>
+* <a href="#_prototypesymboliterator">`_.prototype[Symbol.iterator]`</a>
+* <a href="#_prototypeatpaths">`_.prototype.at`</a>
+* <a href="#_prototypechain">`_.prototype.chain`</a>
+* <a href="#_prototypecommit">`_.prototype.commit`</a>
+* <a href="#_prototypenext">`_.prototype.next`</a>
+* <a href="#_prototypeplantvalue">`_.prototype.plant`</a>
+* <a href="#_prototypereverse">`_.prototype.reverse`</a>
+* <a href="#_prototypevalue" class="alias">`_.prototype.toJSON` -> `value`</a>
+* <a href="#_prototypevalue">`_.prototype.value`</a>
+* <a href="#_prototypevalue" class="alias">`_.prototype.valueOf` -> `value`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `String`
+* <a href="#_camelcasestring">`_.camelCase`</a>
+* <a href="#_capitalizestring">`_.capitalize`</a>
+* <a href="#_deburrstring">`_.deburr`</a>
+* <a href="#_endswithstring-target-positionstringlength">`_.endsWith`</a>
+* <a href="#_escapestring">`_.escape`</a>
+* <a href="#_escaperegexpstring">`_.escapeRegExp`</a>
+* <a href="#_kebabcasestring">`_.kebabCase`</a>
+* <a href="#_lowercasestring">`_.lowerCase`</a>
+* <a href="#_lowerfirststring">`_.lowerFirst`</a>
+* <a href="#_padstring-length0-chars">`_.pad`</a>
+* <a href="#_padendstring-length0-chars">`_.padEnd`</a>
+* <a href="#_padstartstring-length0-chars">`_.padStart`</a>
+* <a href="#_parseintstring-radix10">`_.parseInt`</a>
+* <a href="#_repeatstring-n1">`_.repeat`</a>
+* <a href="#_replacestring-pattern-replacement">`_.replace`</a>
+* <a href="#_snakecasestring">`_.snakeCase`</a>
+* <a href="#_splitstring-separator-limit">`_.split`</a>
+* <a href="#_startcasestring">`_.startCase`</a>
+* <a href="#_startswithstring-target-position0">`_.startsWith`</a>
+* <a href="#_templatestring-options-optionsescape_templatesettingsescape-optionsevaluate_templatesettingsevaluate-optionsimports_templatesettingsimports-optionsinterpolate_templatesettingsinterpolate-optionssourceurllodashtemplatesourcesn-optionsvariableobj">`_.template`</a>
+* <a href="#_tolowerstring">`_.toLower`</a>
+* <a href="#_toupperstring">`_.toUpper`</a>
+* <a href="#_trimstring-charswhitespace">`_.trim`</a>
+* <a href="#_trimendstring-charswhitespace">`_.trimEnd`</a>
+* <a href="#_trimstartstring-charswhitespace">`_.trimStart`</a>
+* <a href="#_truncatestring-options-optionslength30-optionsomission-optionsseparator">`_.truncate`</a>
+* <a href="#_unescapestring">`_.unescape`</a>
+* <a href="#_uppercasestring">`_.upperCase`</a>
+* <a href="#_upperfirststring">`_.upperFirst`</a>
+* <a href="#_wordsstring-pattern">`_.words`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Util`
+* <a href="#_attemptfunc-args">`_.attempt`</a>
+* <a href="#_bindallobject-methodnames">`_.bindAll`</a>
+* <a href="#_condpairs">`_.cond`</a>
+* <a href="#_conformssource">`_.conforms`</a>
+* <a href="#_constantvalue">`_.constant`</a>
+* <a href="#_flowfuncs">`_.flow`</a>
+* <a href="#_flowrightfuncs">`_.flowRight`</a>
+* <a href="#_identityvalue">`_.identity`</a>
+* <a href="#_iterateefunc_identity">`_.iteratee`</a>
+* <a href="#_matchessource">`_.matches`</a>
+* <a href="#_matchespropertypath-srcvalue">`_.matchesProperty`</a>
+* <a href="#_methodpath-args">`_.method`</a>
+* <a href="#_methodofobject-args">`_.methodOf`</a>
+* <a href="#_mixinobjectlodash-source-options-optionschaintrue">`_.mixin`</a>
+* <a href="#_noconflict">`_.noConflict`</a>
+* <a href="#_noop">`_.noop`</a>
+* <a href="#_nthargn0">`_.nthArg`</a>
+* <a href="#_overiteratees_identity">`_.over`</a>
+* <a href="#_overeverypredicates_identity">`_.overEvery`</a>
+* <a href="#_oversomepredicates_identity">`_.overSome`</a>
+* <a href="#_propertypath">`_.property`</a>
+* <a href="#_propertyofobject">`_.propertyOf`</a>
+* <a href="#_rangestart0-end-step1">`_.range`</a>
+* <a href="#_rangerightstart0-end-step1">`_.rangeRight`</a>
+* <a href="#_runincontextcontextroot">`_.runInContext`</a>
+* <a href="#_timesn-iteratee_identity">`_.times`</a>
+* <a href="#_topathvalue">`_.toPath`</a>
+* <a href="#_uniqueidprefix">`_.uniqueId`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Properties`
+* <a href="#_version">`_.VERSION`</a>
+* <a href="#_templatesettings">`_.templateSettings`</a>
+* <a href="#_templatesettingsescape">`_.templateSettings.escape`</a>
+* <a href="#_templatesettingsevaluate">`_.templateSettings.evaluate`</a>
+* <a href="#_templatesettingsimports">`_.templateSettings.imports`</a>
+* <a href="#_templatesettingsinterpolate">`_.templateSettings.interpolate`</a>
+* <a href="#_templatesettingsvariable">`_.templateSettings.variable`</a>
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Methods`
+* <a href="#_templatesettingsimports_">`_.templateSettings.imports._`</a>
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div class="doc-container" -->
+
+<!-- div -->
+
+## `“Array” Methods`
+
+<!-- div -->
+
+### <a id="_chunkarray-size1"></a>`_.chunk(array, [size=1])`
+<a href="#_chunkarray-size1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L5982 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.chunk "See the npm package")
+
+Creates an array of elements split into groups the length of `size`.
+If `array` can't be split evenly, the final chunk will be the remaining
+elements.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to process.
+2. `[size=1]` *(number)*: The length of each chunk
+
+#### Returns
+*(Array)*: Returns the new array containing chunks.
+
+#### Example
+```js
+_.chunk(['a', 'b', 'c', 'd'], 2);
+// => [['a', 'b'], ['c', 'd']]
+
+_.chunk(['a', 'b', 'c', 'd'], 3);
+// => [['a', 'b', 'c'], ['d']]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_compactarray"></a>`_.compact(array)`
+<a href="#_compactarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6017 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.compact "See the npm package")
+
+Creates an array with all falsey values removed. The values `false`, `null`,
+`0`, `""`, `undefined`, and `NaN` are falsey.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to compact.
+
+#### Returns
+*(Array)*: Returns the new array of filtered values.
+
+#### Example
+```js
+_.compact([0, 1, false, 2, '', 3]);
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_concatarray-values"></a>`_.concat(array, [values])`
+<a href="#_concatarray-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6054 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.concat "See the npm package")
+
+Creates a new array concatenating `array` with any additional arrays
+and/or values.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to concatenate.
+2. `[values]` *(...&#42;)*: The values to concatenate.
+
+#### Returns
+*(Array)*: Returns the new concatenated array.
+
+#### Example
+```js
+var array = [1];
+var other = _.concat(array, 2, [3], [[4]]);
+
+console.log(other);
+// => [1, 2, 3, [4]]
+
+console.log(array);
+// => [1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_differencearray-values"></a>`_.difference(array, [values])`
+<a href="#_differencearray-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6087 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.difference "See the npm package")
+
+Creates an array of unique `array` values not included in the other given
+arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons. The order of result values is determined by the
+order they occur in the first array.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+2. `[values]` *(...Array)*: The values to exclude.
+
+#### Returns
+*(Array)*: Returns the new array of filtered values.
+
+#### Example
+```js
+_.difference([3, 2, 1], [4, 2]);
+// => [3, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_differencebyarray-values-iteratee_identity"></a>`_.differenceBy(array, [values], [iteratee=_.identity])`
+<a href="#_differencebyarray-values-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6117 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.differenceby "See the npm package")
+
+This method is like `_.difference` except that it accepts `iteratee` which
+is invoked for each element of `array` and `values` to generate the criterion
+by which they're compared. Result values are chosen from the first array.
+The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+2. `[values]` *(...Array)*: The values to exclude.
+3. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of filtered values.
+
+#### Example
+```js
+_.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
+// => [3.1, 1.3]
+
+// The `_.property` iteratee shorthand.
+_.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+// => [{ 'x': 2 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_differencewitharray-values-comparator"></a>`_.differenceWith(array, [values], [comparator])`
+<a href="#_differencewitharray-values-comparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6148 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.differencewith "See the npm package")
+
+This method is like `_.difference` except that it accepts `comparator`
+which is invoked to compare elements of `array` to `values`. Result values
+are chosen from the first array. The comparator is invoked with two arguments:<br>
+*(arrVal, othVal)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+2. `[values]` *(...Array)*: The values to exclude.
+3. `[comparator]` *(Function)*: The comparator invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of filtered values.
+
+#### Example
+```js
+var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+
+_.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+// => [{ 'x': 2, 'y': 1 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_droparray-n1"></a>`_.drop(array, [n=1])`
+<a href="#_droparray-n1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6183 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.drop "See the npm package")
+
+Creates a slice of `array` with `n` elements dropped from the beginning.
+
+#### Since
+0.5.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[n=1]` *(number)*: The number of elements to drop.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+_.drop([1, 2, 3]);
+// => [2, 3]
+
+_.drop([1, 2, 3], 2);
+// => [3]
+
+_.drop([1, 2, 3], 5);
+// => []
+
+_.drop([1, 2, 3], 0);
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_droprightarray-n1"></a>`_.dropRight(array, [n=1])`
+<a href="#_droprightarray-n1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6217 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.dropright "See the npm package")
+
+Creates a slice of `array` with `n` elements dropped from the end.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[n=1]` *(number)*: The number of elements to drop.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+_.dropRight([1, 2, 3]);
+// => [1, 2]
+
+_.dropRight([1, 2, 3], 2);
+// => [1]
+
+_.dropRight([1, 2, 3], 5);
+// => []
+
+_.dropRight([1, 2, 3], 0);
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_droprightwhilearray-predicate_identity"></a>`_.dropRightWhile(array, [predicate=_.identity])`
+<a href="#_droprightwhilearray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6263 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.droprightwhile "See the npm package")
+
+Creates a slice of `array` excluding elements dropped from the end.
+Elements are dropped until `predicate` returns falsey. The predicate is
+invoked with three arguments: *(value, index, array)*.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'active': true },
+  { 'user': 'fred',    'active': false },
+  { 'user': 'pebbles', 'active': false }
+];
+
+_.dropRightWhile(users, function(o) { return !o.active; });
+// => objects for ['barney']
+
+// The `_.matches` iteratee shorthand.
+_.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+// => objects for ['barney', 'fred']
+
+// The `_.matchesProperty` iteratee shorthand.
+_.dropRightWhile(users, ['active', false]);
+// => objects for ['barney']
+
+// The `_.property` iteratee shorthand.
+_.dropRightWhile(users, 'active');
+// => objects for ['barney', 'fred', 'pebbles']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_dropwhilearray-predicate_identity"></a>`_.dropWhile(array, [predicate=_.identity])`
+<a href="#_dropwhilearray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6305 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.dropwhile "See the npm package")
+
+Creates a slice of `array` excluding elements dropped from the beginning.
+Elements are dropped until `predicate` returns falsey. The predicate is
+invoked with three arguments: *(value, index, array)*.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'active': false },
+  { 'user': 'fred',    'active': false },
+  { 'user': 'pebbles', 'active': true }
+];
+
+_.dropWhile(users, function(o) { return !o.active; });
+// => objects for ['pebbles']
+
+// The `_.matches` iteratee shorthand.
+_.dropWhile(users, { 'user': 'barney', 'active': false });
+// => objects for ['fred', 'pebbles']
+
+// The `_.matchesProperty` iteratee shorthand.
+_.dropWhile(users, ['active', false]);
+// => objects for ['pebbles']
+
+// The `_.property` iteratee shorthand.
+_.dropWhile(users, 'active');
+// => objects for ['barney', 'fred', 'pebbles']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_fillarray-value-start0-endarraylength"></a>`_.fill(array, value, [start=0], [end=array.length])`
+<a href="#_fillarray-value-start0-endarraylength">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6340 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.fill "See the npm package")
+
+Fills elements of `array` with `value` from `start` up to, but not
+including, `end`.
+<br>
+<br>
+**Note:** This method mutates `array`.
+
+#### Since
+3.2.0
+#### Arguments
+1. `array` *(Array)*: The array to fill.
+2. `value` *(&#42;)*: The value to fill `array` with.
+3. `[start=0]` *(number)*: The start position.
+4. `[end=array.length]` *(number)*: The end position.
+
+#### Returns
+*(Array)*: Returns `array`.
+
+#### Example
+```js
+var array = [1, 2, 3];
+
+_.fill(array, 'a');
+console.log(array);
+// => ['a', 'a', 'a']
+
+_.fill(Array(3), 2);
+// => [2, 2, 2]
+
+_.fill([4, 6, 8, 10], '*', 1, 3);
+// => [4, '*', '*', 10]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_findindexarray-predicate_identity"></a>`_.findIndex(array, [predicate=_.identity])`
+<a href="#_findindexarray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6387 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.findindex "See the npm package")
+
+This method is like `_.find` except that it returns the index of the first
+element `predicate` returns truthy for instead of the element itself.
+
+#### Since
+1.1.0
+#### Arguments
+1. `array` *(Array)*: The array to search.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(number)*: Returns the index of the found element, else `-1`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'active': false },
+  { 'user': 'fred',    'active': false },
+  { 'user': 'pebbles', 'active': true }
+];
+
+_.findIndex(users, function(o) { return o.user == 'barney'; });
+// => 0
+
+// The `_.matches` iteratee shorthand.
+_.findIndex(users, { 'user': 'fred', 'active': false });
+// => 1
+
+// The `_.matchesProperty` iteratee shorthand.
+_.findIndex(users, ['active', false]);
+// => 0
+
+// The `_.property` iteratee shorthand.
+_.findIndex(users, 'active');
+// => 2
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_findlastindexarray-predicate_identity"></a>`_.findLastIndex(array, [predicate=_.identity])`
+<a href="#_findlastindexarray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6428 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.findlastindex "See the npm package")
+
+This method is like `_.findIndex` except that it iterates over elements
+of `collection` from right to left.
+
+#### Since
+2.0.0
+#### Arguments
+1. `array` *(Array)*: The array to search.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(number)*: Returns the index of the found element, else `-1`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'active': true },
+  { 'user': 'fred',    'active': false },
+  { 'user': 'pebbles', 'active': false }
+];
+
+_.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+// => 2
+
+// The `_.matches` iteratee shorthand.
+_.findLastIndex(users, { 'user': 'barney', 'active': true });
+// => 0
+
+// The `_.matchesProperty` iteratee shorthand.
+_.findLastIndex(users, ['active', false]);
+// => 2
+
+// The `_.property` iteratee shorthand.
+_.findLastIndex(users, 'active');
+// => 0
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flattenarray"></a>`_.flatten(array)`
+<a href="#_flattenarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6448 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flatten "See the npm package")
+
+Flattens `array` a single level deep.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to flatten.
+
+#### Returns
+*(Array)*: Returns the new flattened array.
+
+#### Example
+```js
+_.flatten([1, [2, [3, [4]], 5]]);
+// => [1, 2, [3, [4]], 5]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flattendeeparray"></a>`_.flattenDeep(array)`
+<a href="#_flattendeeparray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6467 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flattendeep "See the npm package")
+
+Recursively flattens `array`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to flatten.
+
+#### Returns
+*(Array)*: Returns the new flattened array.
+
+#### Example
+```js
+_.flattenDeep([1, [2, [3, [4]], 5]]);
+// => [1, 2, 3, 4, 5]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flattendeptharray-depth1"></a>`_.flattenDepth(array, [depth=1])`
+<a href="#_flattendeptharray-depth1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6492 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flattendepth "See the npm package")
+
+Recursively flatten `array` up to `depth` times.
+
+#### Since
+4.4.0
+#### Arguments
+1. `array` *(Array)*: The array to flatten.
+2. `[depth=1]` *(number)*: The maximum recursion depth.
+
+#### Returns
+*(Array)*: Returns the new flattened array.
+
+#### Example
+```js
+var array = [1, [2, [3, [4]], 5]];
+
+_.flattenDepth(array, 1);
+// => [1, 2, [3, [4]], 5]
+
+_.flattenDepth(array, 2);
+// => [1, 2, 3, [4], 5]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_frompairspairs"></a>`_.fromPairs(pairs)`
+<a href="#_frompairspairs">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6516 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.frompairs "See the npm package")
+
+The inverse of `_.toPairs`; this method returns an object composed
+from key-value `pairs`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `pairs` *(Array)*: The key-value pairs.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+_.fromPairs([['fred', 30], ['barney', 40]]);
+// => { 'fred': 30, 'barney': 40 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_headarray"></a>`_.head(array)`
+<a href="#_headarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6546 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.head "See the npm package")
+
+Gets the first element of `array`.
+
+#### Since
+0.1.0
+#### Aliases
+*_.first*
+
+#### Arguments
+1. `array` *(Array)*: The array to query.
+
+#### Returns
+*(&#42;)*: Returns the first element of `array`.
+
+#### Example
+```js
+_.head([1, 2, 3]);
+// => 1
+
+_.head([]);
+// => undefined
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_indexofarray-value-fromindex0"></a>`_.indexOf(array, value, [fromIndex=0])`
+<a href="#_indexofarray-value-fromindex0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6573 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.indexof "See the npm package")
+
+Gets the index at which the first occurrence of `value` is found in `array`
+using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons. If `fromIndex` is negative, it's used as the
+offset from the end of `array`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to search.
+2. `value` *(&#42;)*: The value to search for.
+3. `[fromIndex=0]` *(number)*: The index to search from.
+
+#### Returns
+*(number)*: Returns the index of the matched value, else `-1`.
+
+#### Example
+```js
+_.indexOf([1, 2, 1, 2], 2);
+// => 1
+
+// Search from the `fromIndex`.
+_.indexOf([1, 2, 1, 2], 2, 2);
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_initialarray"></a>`_.initial(array)`
+<a href="#_initialarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6599 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.initial "See the npm package")
+
+Gets all but the last element of `array`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+_.initial([1, 2, 3]);
+// => [1, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_intersectionarrays"></a>`_.intersection([arrays])`
+<a href="#_intersectionarrays">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6620 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.intersection "See the npm package")
+
+Creates an array of unique values that are included in all given arrays
+using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons. The order of result values is determined by the
+order they occur in the first array.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+
+#### Returns
+*(Array)*: Returns the new array of intersecting values.
+
+#### Example
+```js
+_.intersection([2, 1], [4, 2], [1, 2]);
+// => [2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_intersectionbyarrays-iteratee_identity"></a>`_.intersectionBy([arrays], [iteratee=_.identity])`
+<a href="#_intersectionbyarrays-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6650 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.intersectionby "See the npm package")
+
+This method is like `_.intersection` except that it accepts `iteratee`
+which is invoked for each element of each `arrays` to generate the criterion
+by which they're compared. Result values are chosen from the first array.
+The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of intersecting values.
+
+#### Example
+```js
+_.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+// => [2.1]
+
+// The `_.property` iteratee shorthand.
+_.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+// => [{ 'x': 1 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_intersectionwitharrays-comparator"></a>`_.intersectionWith([arrays], [comparator])`
+<a href="#_intersectionwitharrays-comparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6685 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.intersectionwith "See the npm package")
+
+This method is like `_.intersection` except that it accepts `comparator`
+which is invoked to compare elements of `arrays`. Result values are chosen
+from the first array. The comparator is invoked with two arguments:<br>
+*(arrVal, othVal)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+2. `[comparator]` *(Function)*: The comparator invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of intersecting values.
+
+#### Example
+```js
+var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+
+_.intersectionWith(objects, others, _.isEqual);
+// => [{ 'x': 1, 'y': 2 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_joinarray-separator-"></a>`_.join(array, [separator=','])`
+<a href="#_joinarray-separator-">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6714 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.join "See the npm package")
+
+Converts all elements in `array` into a string separated by `separator`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to convert.
+2. `[separator=',']` *(string)*: The element separator.
+
+#### Returns
+*(string)*: Returns the joined string.
+
+#### Example
+```js
+_.join(['a', 'b', 'c'], '~');
+// => 'a~b~c'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_lastarray"></a>`_.last(array)`
+<a href="#_lastarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6732 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.last "See the npm package")
+
+Gets the last element of `array`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+
+#### Returns
+*(&#42;)*: Returns the last element of `array`.
+
+#### Example
+```js
+_.last([1, 2, 3]);
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_lastindexofarray-value-fromindexarraylength-1"></a>`_.lastIndexOf(array, value, [fromIndex=array.length-1])`
+<a href="#_lastindexofarray-value-fromindexarraylength-1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6758 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.lastindexof "See the npm package")
+
+This method is like `_.indexOf` except that it iterates over elements of
+`array` from right to left.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to search.
+2. `value` *(&#42;)*: The value to search for.
+3. `[fromIndex=array.length-1]` *(number)*: The index to search from.
+
+#### Returns
+*(number)*: Returns the index of the matched value, else `-1`.
+
+#### Example
+```js
+_.lastIndexOf([1, 2, 1, 2], 2);
+// => 3
+
+// Search from the `fromIndex`.
+_.lastIndexOf([1, 2, 1, 2], 2, 2);
+// => 1
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ntharray-n0"></a>`_.nth(array, [n=0])`
+<a href="#_ntharray-n0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6804 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.nth "See the npm package")
+
+Gets the nth element of `array`. If `n` is negative, the nth element
+from the end is returned.
+
+#### Since
+4.11.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[n=0]` *(number)*: The index of the element to return.
+
+#### Returns
+*(&#42;)*: Returns the nth element of `array`.
+
+#### Example
+```js
+var array = ['a', 'b', 'c', 'd'];
+
+_.nth(array, 1);
+// => 'b'
+
+_.nth(array, -2);
+// => 'c';
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pullarray-values"></a>`_.pull(array, [values])`
+<a href="#_pullarray-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6831 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pull "See the npm package")
+
+Removes all given values from `array` using
+[`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons.
+<br>
+<br>
+**Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+to remove elements from an array by predicate.
+
+#### Since
+2.0.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+2. `[values]` *(...&#42;)*: The values to remove.
+
+#### Returns
+*(Array)*: Returns `array`.
+
+#### Example
+```js
+var array = [1, 2, 3, 1, 2, 3];
+
+_.pull(array, 2, 3);
+console.log(array);
+// => [1, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pullallarray-values"></a>`_.pullAll(array, values)`
+<a href="#_pullallarray-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6853 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pullall "See the npm package")
+
+This method is like `_.pull` except that it accepts an array of values to remove.
+<br>
+<br>
+**Note:** Unlike `_.difference`, this method mutates `array`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+2. `values` *(Array)*: The values to remove.
+
+#### Returns
+*(Array)*: Returns `array`.
+
+#### Example
+```js
+var array = [1, 2, 3, 1, 2, 3];
+
+_.pullAll(array, [2, 3]);
+console.log(array);
+// => [1, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pullallbyarray-values-iteratee_identity"></a>`_.pullAllBy(array, values, [iteratee=_.identity])`
+<a href="#_pullallbyarray-values-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6883 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pullallby "See the npm package")
+
+This method is like `_.pullAll` except that it accepts `iteratee` which is
+invoked for each element of `array` and `values` to generate the criterion
+by which they're compared. The iteratee is invoked with one argument: *(value)*.
+<br>
+<br>
+**Note:** Unlike `_.differenceBy`, this method mutates `array`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+2. `values` *(Array)*: The values to remove.
+3. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns `array`.
+
+#### Example
+```js
+var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+
+_.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+console.log(array);
+// => [{ 'x': 2 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pullallwitharray-values-comparator"></a>`_.pullAllWith(array, values, [comparator])`
+<a href="#_pullallwitharray-values-comparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6912 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pullallwith "See the npm package")
+
+This method is like `_.pullAll` except that it accepts `comparator` which
+is invoked to compare elements of `array` to `values`. The comparator is
+invoked with two arguments: *(arrVal, othVal)*.
+<br>
+<br>
+**Note:** Unlike `_.differenceWith`, this method mutates `array`.
+
+#### Since
+4.6.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+2. `values` *(Array)*: The values to remove.
+3. `[comparator]` *(Function)*: The comparator invoked per element.
+
+#### Returns
+*(Array)*: Returns `array`.
+
+#### Example
+```js
+var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+
+_.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+console.log(array);
+// => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pullatarray-indexes"></a>`_.pullAt(array, [indexes])`
+<a href="#_pullatarray-indexes">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6942 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pullat "See the npm package")
+
+Removes elements from `array` corresponding to `indexes` and returns an
+array of removed elements.
+<br>
+<br>
+**Note:** Unlike `_.at`, this method mutates `array`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+2. `[indexes]` *(...(number|number&#91;&#93;))*: The indexes of elements to remove.
+
+#### Returns
+*(Array)*: Returns the new array of removed elements.
+
+#### Example
+```js
+var array = [5, 10, 15, 20];
+var evens = _.pullAt(array, 1, 3);
+
+console.log(array);
+// => [5, 15]
+
+console.log(evens);
+// => [10, 20]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_removearray-predicate_identity"></a>`_.remove(array, [predicate=_.identity])`
+<a href="#_removearray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L6984 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.remove "See the npm package")
+
+Removes all elements from `array` that `predicate` returns truthy for
+and returns an array of the removed elements. The predicate is invoked
+with three arguments: *(value, index, array)*.
+<br>
+<br>
+**Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+to pull elements from an array by value.
+
+#### Since
+2.0.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the new array of removed elements.
+
+#### Example
+```js
+var array = [1, 2, 3, 4];
+var evens = _.remove(array, function(n) {
+  return n % 2 == 0;
+});
+
+console.log(array);
+// => [1, 3]
+
+console.log(evens);
+// => [2, 4]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_reversearray"></a>`_.reverse(array)`
+<a href="#_reversearray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7028 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.reverse "See the npm package")
+
+Reverses `array` so that the first element becomes the last, the second
+element becomes the second to last, and so on.
+<br>
+<br>
+**Note:** This method mutates `array` and is based on
+[`Array#reverse`](https://mdn.io/Array/reverse).
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to modify.
+
+#### Returns
+*(Array)*: Returns `array`.
+
+#### Example
+```js
+var array = [1, 2, 3];
+
+_.reverse(array);
+// => [3, 2, 1]
+
+console.log(array);
+// => [3, 2, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_slicearray-start0-endarraylength"></a>`_.slice(array, [start=0], [end=array.length])`
+<a href="#_slicearray-start0-endarraylength">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7048 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.slice "See the npm package")
+
+Creates a slice of `array` from `start` up to, but not including, `end`.
+<br>
+<br>
+**Note:** This method is used instead of
+[`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+returned.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to slice.
+2. `[start=0]` *(number)*: The start position.
+3. `[end=array.length]` *(number)*: The end position.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortedindexarray-value"></a>`_.sortedIndex(array, value)`
+<a href="#_sortedindexarray-value">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7084 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortedindex "See the npm package")
+
+Uses a binary search to determine the lowest index at which `value`
+should be inserted into `array` in order to maintain its sort order.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The sorted array to inspect.
+2. `value` *(&#42;)*: The value to evaluate.
+
+#### Returns
+*(number)*: Returns the index at which `value` should be inserted into `array`.
+
+#### Example
+```js
+_.sortedIndex([30, 50], 40);
+// => 1
+
+_.sortedIndex([4, 5], 4);
+// => 0
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortedindexbyarray-value-iteratee_identity"></a>`_.sortedIndexBy(array, value, [iteratee=_.identity])`
+<a href="#_sortedindexbyarray-value-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7114 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortedindexby "See the npm package")
+
+This method is like `_.sortedIndex` except that it accepts `iteratee`
+which is invoked for `value` and each element of `array` to compute their
+sort ranking. The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The sorted array to inspect.
+2. `value` *(&#42;)*: The value to evaluate.
+3. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(number)*: Returns the index at which `value` should be inserted into `array`.
+
+#### Example
+```js
+var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };
+
+_.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));
+// => 1
+
+// The `_.property` iteratee shorthand.
+_.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+// => 0
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortedindexofarray-value"></a>`_.sortedIndexOf(array, value)`
+<a href="#_sortedindexofarray-value">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7134 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortedindexof "See the npm package")
+
+This method is like `_.indexOf` except that it performs a binary
+search on a sorted `array`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to search.
+2. `value` *(&#42;)*: The value to search for.
+
+#### Returns
+*(number)*: Returns the index of the matched value, else `-1`.
+
+#### Example
+```js
+_.sortedIndexOf([1, 1, 2, 2], 2);
+// => 2
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortedlastindexarray-value"></a>`_.sortedLastIndex(array, value)`
+<a href="#_sortedlastindexarray-value">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7163 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortedlastindex "See the npm package")
+
+This method is like `_.sortedIndex` except that it returns the highest
+index at which `value` should be inserted into `array` in order to
+maintain its sort order.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The sorted array to inspect.
+2. `value` *(&#42;)*: The value to evaluate.
+
+#### Returns
+*(number)*: Returns the index at which `value` should be inserted into `array`.
+
+#### Example
+```js
+_.sortedLastIndex([4, 5], 4);
+// => 1
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortedlastindexbyarray-value-iteratee_identity"></a>`_.sortedLastIndexBy(array, value, [iteratee=_.identity])`
+<a href="#_sortedlastindexbyarray-value-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7188 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortedlastindexby "See the npm package")
+
+This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+which is invoked for `value` and each element of `array` to compute their
+sort ranking. The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The sorted array to inspect.
+2. `value` *(&#42;)*: The value to evaluate.
+3. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(number)*: Returns the index at which `value` should be inserted into `array`.
+
+#### Example
+```js
+// The `_.property` iteratee shorthand.
+_.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+// => 1
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortedlastindexofarray-value"></a>`_.sortedLastIndexOf(array, value)`
+<a href="#_sortedlastindexofarray-value">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7208 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortedlastindexof "See the npm package")
+
+This method is like `_.lastIndexOf` except that it performs a binary
+search on a sorted `array`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to search.
+2. `value` *(&#42;)*: The value to search for.
+
+#### Returns
+*(number)*: Returns the index of the matched value, else `-1`.
+
+#### Example
+```js
+_.sortedLastIndexOf([1, 1, 2, 2], 2);
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sorteduniqarray"></a>`_.sortedUniq(array)`
+<a href="#_sorteduniqarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7234 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sorteduniq "See the npm package")
+
+This method is like `_.uniq` except that it's designed and optimized
+for sorted arrays.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+
+#### Returns
+*(Array)*: Returns the new duplicate free array.
+
+#### Example
+```js
+_.sortedUniq([1, 1, 2]);
+// => [1, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sorteduniqbyarray-iteratee"></a>`_.sortedUniqBy(array, [iteratee])`
+<a href="#_sorteduniqbyarray-iteratee">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7256 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sorteduniqby "See the npm package")
+
+This method is like `_.uniqBy` except that it's designed and optimized
+for sorted arrays.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+2. `[iteratee]` *(Function)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns the new duplicate free array.
+
+#### Example
+```js
+_.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+// => [1.1, 2.3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tailarray"></a>`_.tail(array)`
+<a href="#_tailarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7276 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tail "See the npm package")
+
+Gets all but the first element of `array`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+_.tail([1, 2, 3]);
+// => [2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_takearray-n1"></a>`_.take(array, [n=1])`
+<a href="#_takearray-n1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7305 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.take "See the npm package")
+
+Creates a slice of `array` with `n` elements taken from the beginning.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[n=1]` *(number)*: The number of elements to take.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+_.take([1, 2, 3]);
+// => [1]
+
+_.take([1, 2, 3], 2);
+// => [1, 2]
+
+_.take([1, 2, 3], 5);
+// => [1, 2, 3]
+
+_.take([1, 2, 3], 0);
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_takerightarray-n1"></a>`_.takeRight(array, [n=1])`
+<a href="#_takerightarray-n1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7338 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.takeright "See the npm package")
+
+Creates a slice of `array` with `n` elements taken from the end.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[n=1]` *(number)*: The number of elements to take.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+_.takeRight([1, 2, 3]);
+// => [3]
+
+_.takeRight([1, 2, 3], 2);
+// => [2, 3]
+
+_.takeRight([1, 2, 3], 5);
+// => [1, 2, 3]
+
+_.takeRight([1, 2, 3], 0);
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_takerightwhilearray-predicate_identity"></a>`_.takeRightWhile(array, [predicate=_.identity])`
+<a href="#_takerightwhilearray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7384 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.takerightwhile "See the npm package")
+
+Creates a slice of `array` with elements taken from the end. Elements are
+taken until `predicate` returns falsey. The predicate is invoked with
+three arguments: *(value, index, array)*.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'active': true },
+  { 'user': 'fred',    'active': false },
+  { 'user': 'pebbles', 'active': false }
+];
+
+_.takeRightWhile(users, function(o) { return !o.active; });
+// => objects for ['fred', 'pebbles']
+
+// The `_.matches` iteratee shorthand.
+_.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+// => objects for ['pebbles']
+
+// The `_.matchesProperty` iteratee shorthand.
+_.takeRightWhile(users, ['active', false]);
+// => objects for ['fred', 'pebbles']
+
+// The `_.property` iteratee shorthand.
+_.takeRightWhile(users, 'active');
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_takewhilearray-predicate_identity"></a>`_.takeWhile(array, [predicate=_.identity])`
+<a href="#_takewhilearray-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7426 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.takewhile "See the npm package")
+
+Creates a slice of `array` with elements taken from the beginning. Elements
+are taken until `predicate` returns falsey. The predicate is invoked with
+three arguments: *(value, index, array)*.
+
+#### Since
+3.0.0
+#### Arguments
+1. `array` *(Array)*: The array to query.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the slice of `array`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'active': false },
+  { 'user': 'fred',    'active': false},
+  { 'user': 'pebbles', 'active': true }
+];
+
+_.takeWhile(users, function(o) { return !o.active; });
+// => objects for ['barney', 'fred']
+
+// The `_.matches` iteratee shorthand.
+_.takeWhile(users, { 'user': 'barney', 'active': false });
+// => objects for ['barney']
+
+// The `_.matchesProperty` iteratee shorthand.
+_.takeWhile(users, ['active', false]);
+// => objects for ['barney', 'fred']
+
+// The `_.property` iteratee shorthand.
+_.takeWhile(users, 'active');
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unionarrays"></a>`_.union([arrays])`
+<a href="#_unionarrays">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7448 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.union "See the npm package")
+
+Creates an array of unique values, in order, from all given arrays using
+[`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+
+#### Returns
+*(Array)*: Returns the new array of combined values.
+
+#### Example
+```js
+_.union([2, 1], [4, 2], [1, 2]);
+// => [2, 1, 4]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unionbyarrays-iteratee_identity"></a>`_.unionBy([arrays], [iteratee=_.identity])`
+<a href="#_unionbyarrays-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7475 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unionby "See the npm package")
+
+This method is like `_.union` except that it accepts `iteratee` which is
+invoked for each element of each `arrays` to generate the criterion by
+which uniqueness is computed. The iteratee is invoked with one argument:<br>
+*(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of combined values.
+
+#### Example
+```js
+_.unionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+// => [2.1, 1.2, 4.3]
+
+// The `_.property` iteratee shorthand.
+_.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+// => [{ 'x': 1 }, { 'x': 2 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unionwitharrays-comparator"></a>`_.unionWith([arrays], [comparator])`
+<a href="#_unionwitharrays-comparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7503 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unionwith "See the npm package")
+
+This method is like `_.union` except that it accepts `comparator` which
+is invoked to compare elements of `arrays`. The comparator is invoked
+with two arguments: *(arrVal, othVal)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+2. `[comparator]` *(Function)*: The comparator invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of combined values.
+
+#### Example
+```js
+var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+
+_.unionWith(objects, others, _.isEqual);
+// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_uniqarray"></a>`_.uniq(array)`
+<a href="#_uniqarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7528 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.uniq "See the npm package")
+
+Creates a duplicate-free version of an array, using
+[`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons, in which only the first occurrence of each
+element is kept.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+
+#### Returns
+*(Array)*: Returns the new duplicate free array.
+
+#### Example
+```js
+_.uniq([2, 1, 2]);
+// => [2, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_uniqbyarray-iteratee_identity"></a>`_.uniqBy(array, [iteratee=_.identity])`
+<a href="#_uniqbyarray-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7556 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.uniqby "See the npm package")
+
+This method is like `_.uniq` except that it accepts `iteratee` which is
+invoked for each element in `array` to generate the criterion by which
+uniqueness is computed. The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns the new duplicate free array.
+
+#### Example
+```js
+_.uniqBy([2.1, 1.2, 2.3], Math.floor);
+// => [2.1, 1.2]
+
+// The `_.property` iteratee shorthand.
+_.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+// => [{ 'x': 1 }, { 'x': 2 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_uniqwitharray-comparator"></a>`_.uniqWith(array, [comparator])`
+<a href="#_uniqwitharray-comparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7581 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.uniqwith "See the npm package")
+
+This method is like `_.uniq` except that it accepts `comparator` which
+is invoked to compare elements of `array`. The comparator is invoked with
+two arguments: *(arrVal, othVal)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to inspect.
+2. `[comparator]` *(Function)*: The comparator invoked per element.
+
+#### Returns
+*(Array)*: Returns the new duplicate free array.
+
+#### Example
+```js
+var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 },  { 'x': 1, 'y': 2 }];
+
+_.uniqWith(objects, _.isEqual);
+// => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unziparray"></a>`_.unzip(array)`
+<a href="#_unziparray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7606 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unzip "See the npm package")
+
+This method is like `_.zip` except that it accepts an array of grouped
+elements and creates an array regrouping the elements to their pre-zip
+configuration.
+
+#### Since
+1.2.0
+#### Arguments
+1. `array` *(Array)*: The array of grouped elements to process.
+
+#### Returns
+*(Array)*: Returns the new array of regrouped elements.
+
+#### Example
+```js
+var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
+// => [['fred', 30, true], ['barney', 40, false]]
+
+_.unzip(zipped);
+// => [['fred', 'barney'], [30, 40], [true, false]]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unzipwitharray-iteratee_identity"></a>`_.unzipWith(array, [iteratee=_.identity])`
+<a href="#_unzipwitharray-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7643 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unzipwith "See the npm package")
+
+This method is like `_.unzip` except that it accepts `iteratee` to specify
+how regrouped values should be combined. The iteratee is invoked with the
+elements of each group: *(...group)*.
+
+#### Since
+3.8.0
+#### Arguments
+1. `array` *(Array)*: The array of grouped elements to process.
+2. `[iteratee=_.identity]` *(Function)*: The function to combine regrouped values.
+
+#### Returns
+*(Array)*: Returns the new array of regrouped elements.
+
+#### Example
+```js
+var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+// => [[1, 10, 100], [2, 20, 200]]
+
+_.unzipWith(zipped, _.add);
+// => [3, 30, 300]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_withoutarray-values"></a>`_.without(array, [values])`
+<a href="#_withoutarray-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7674 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.without "See the npm package")
+
+Creates an array excluding all given values using
+[`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+for equality comparisons.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to filter.
+2. `[values]` *(...&#42;)*: The values to exclude.
+
+#### Returns
+*(Array)*: Returns the new array of filtered values.
+
+#### Example
+```js
+_.without([1, 2, 1, 3], 1, 2);
+// => [3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_xorarrays"></a>`_.xor([arrays])`
+<a href="#_xorarrays">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7698 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.xor "See the npm package")
+
+Creates an array of unique values that is the
+[symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+of the given arrays. The order of result values is determined by the order
+they occur in the arrays.
+
+#### Since
+2.4.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+
+#### Returns
+*(Array)*: Returns the new array of values.
+
+#### Example
+```js
+_.xor([2, 1], [4, 2]);
+// => [1, 4]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_xorbyarrays-iteratee_identity"></a>`_.xorBy([arrays], [iteratee=_.identity])`
+<a href="#_xorbyarrays-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7725 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.xorby "See the npm package")
+
+This method is like `_.xor` except that it accepts `iteratee` which is
+invoked for each element of each `arrays` to generate the criterion by
+which by which they're compared. The iteratee is invoked with one argument:<br>
+*(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of values.
+
+#### Example
+```js
+_.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+// => [1.2, 4.3]
+
+// The `_.property` iteratee shorthand.
+_.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+// => [{ 'x': 2 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_xorwitharrays-comparator"></a>`_.xorWith([arrays], [comparator])`
+<a href="#_xorwitharrays-comparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7753 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.xorwith "See the npm package")
+
+This method is like `_.xor` except that it accepts `comparator` which is
+invoked to compare elements of `arrays`. The comparator is invoked with
+two arguments: *(arrVal, othVal)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to inspect.
+2. `[comparator]` *(Function)*: The comparator invoked per element.
+
+#### Returns
+*(Array)*: Returns the new array of values.
+
+#### Example
+```js
+var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+
+_.xorWith(objects, others, _.isEqual);
+// => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ziparrays"></a>`_.zip([arrays])`
+<a href="#_ziparrays">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7777 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.zip "See the npm package")
+
+Creates an array of grouped elements, the first of which contains the
+first elements of the given arrays, the second of which contains the
+second elements of the given arrays, and so on.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to process.
+
+#### Returns
+*(Array)*: Returns the new array of grouped elements.
+
+#### Example
+```js
+_.zip(['fred', 'barney'], [30, 40], [true, false]);
+// => [['fred', 30, true], ['barney', 40, false]]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_zipobjectprops-values"></a>`_.zipObject([props=[]], [values=[]])`
+<a href="#_zipobjectprops-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7795 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.zipobject "See the npm package")
+
+This method is like `_.fromPairs` except that it accepts two arrays,
+one of property identifiers and one of corresponding values.
+
+#### Since
+0.4.0
+#### Arguments
+1. `[props=[]]` *(Array)*: The property identifiers.
+2. `[values=[]]` *(Array)*: The property values.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+_.zipObject(['a', 'b'], [1, 2]);
+// => { 'a': 1, 'b': 2 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_zipobjectdeepprops-values"></a>`_.zipObjectDeep([props=[]], [values=[]])`
+<a href="#_zipobjectdeepprops-values">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7814 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.zipobjectdeep "See the npm package")
+
+This method is like `_.zipObject` except that it supports property paths.
+
+#### Since
+4.1.0
+#### Arguments
+1. `[props=[]]` *(Array)*: The property identifiers.
+2. `[values=[]]` *(Array)*: The property values.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+_.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+// => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_zipwitharrays-iteratee_identity"></a>`_.zipWith([arrays], [iteratee=_.identity])`
+<a href="#_zipwitharrays-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7837 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.zipwith "See the npm package")
+
+This method is like `_.zip` except that it accepts `iteratee` to specify
+how grouped values should be combined. The iteratee is invoked with the
+elements of each group: *(...group)*.
+
+#### Since
+3.8.0
+#### Arguments
+1. `[arrays]` *(...Array)*: The arrays to process.
+2. `[iteratee=_.identity]` *(Function)*: The function to combine grouped values.
+
+#### Returns
+*(Array)*: Returns the new array of grouped elements.
+
+#### Example
+```js
+_.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+  return a + b + c;
+});
+// => [111, 222]
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Collection” Methods`
+
+<!-- div -->
+
+### <a id="_countbycollection-iteratee_identity"></a>`_.countBy(collection, [iteratee=_.identity])`
+<a href="#_countbycollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8220 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.countby "See the npm package")
+
+Creates an object composed of keys generated from the results of running
+each element of `collection` thru `iteratee`. The corresponding value of
+each key is the number of times the key was returned by `iteratee`. The
+iteratee is invoked with one argument: *(value)*.
+
+#### Since
+0.5.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee to transform keys.
+
+#### Returns
+*(Object)*: Returns the composed aggregate object.
+
+#### Example
+```js
+_.countBy([6.1, 4.2, 6.3], Math.floor);
+// => { '4': 1, '6': 2 }
+
+_.countBy(['one', 'two', 'three'], 'length');
+// => { '3': 2, '5': 1 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_everycollection-predicate_identity"></a>`_.every(collection, [predicate=_.identity])`
+<a href="#_everycollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8261 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.every "See the npm package")
+
+Checks if `predicate` returns truthy for **all** elements of `collection`.
+Iteration is stopped once `predicate` returns falsey. The predicate is
+invoked with three arguments: *(value, index|key, collection)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(boolean)*: Returns `true` if all elements pass the predicate check, else `false`.
+
+#### Example
+```js
+_.every([true, 1, null, 'yes'], Boolean);
+// => false
+
+var users = [
+  { 'user': 'barney', 'age': 36, 'active': false },
+  { 'user': 'fred',   'age': 40, 'active': false }
+];
+
+// The `_.matches` iteratee shorthand.
+_.every(users, { 'user': 'barney', 'active': false });
+// => false
+
+// The `_.matchesProperty` iteratee shorthand.
+_.every(users, ['active', false]);
+// => true
+
+// The `_.property` iteratee shorthand.
+_.every(users, 'active');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_filtercollection-predicate_identity"></a>`_.filter(collection, [predicate=_.identity])`
+<a href="#_filtercollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8305 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.filter "See the npm package")
+
+Iterates over elements of `collection`, returning an array of all elements
+`predicate` returns truthy for. The predicate is invoked with three
+arguments: *(value, index|key, collection)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the new filtered array.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney', 'age': 36, 'active': true },
+  { 'user': 'fred',   'age': 40, 'active': false }
+];
+
+_.filter(users, function(o) { return !o.active; });
+// => objects for ['fred']
+
+// The `_.matches` iteratee shorthand.
+_.filter(users, { 'age': 36, 'active': true });
+// => objects for ['barney']
+
+// The `_.matchesProperty` iteratee shorthand.
+_.filter(users, ['active', false]);
+// => objects for ['fred']
+
+// The `_.property` iteratee shorthand.
+_.filter(users, 'active');
+// => objects for ['barney']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_findcollection-predicate_identity"></a>`_.find(collection, [predicate=_.identity])`
+<a href="#_findcollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8346 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.find "See the npm package")
+
+Iterates over elements of `collection`, returning the first element
+`predicate` returns truthy for. The predicate is invoked with three
+arguments: *(value, index|key, collection)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to search.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(&#42;)*: Returns the matched element, else `undefined`.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'age': 36, 'active': true },
+  { 'user': 'fred',    'age': 40, 'active': false },
+  { 'user': 'pebbles', 'age': 1,  'active': true }
+];
+
+_.find(users, function(o) { return o.age < 40; });
+// => object for 'barney'
+
+// The `_.matches` iteratee shorthand.
+_.find(users, { 'age': 1, 'active': true });
+// => object for 'pebbles'
+
+// The `_.matchesProperty` iteratee shorthand.
+_.find(users, ['active', false]);
+// => object for 'fred'
+
+// The `_.property` iteratee shorthand.
+_.find(users, 'active');
+// => object for 'barney'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_findlastcollection-predicate_identity"></a>`_.findLast(collection, [predicate=_.identity])`
+<a href="#_findlastcollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8374 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.findlast "See the npm package")
+
+This method is like `_.find` except that it iterates over elements of
+`collection` from right to left.
+
+#### Since
+2.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to search.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(&#42;)*: Returns the matched element, else `undefined`.
+
+#### Example
+```js
+_.findLast([1, 2, 3, 4], function(n) {
+  return n % 2 == 1;
+});
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flatmapcollection-iteratee_identity"></a>`_.flatMap(collection, [iteratee=_.identity])`
+<a href="#_flatmapcollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8405 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flatmap "See the npm package")
+
+Creates a flattened array of values by running each element in `collection`
+thru `iteratee` and flattening the mapped results. The iteratee is invoked
+with three arguments: *(value, index|key, collection)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the new flattened array.
+
+#### Example
+```js
+function duplicate(n) {
+  return [n, n];
+}
+
+_.flatMap([1, 2], duplicate);
+// => [1, 1, 2, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flatmapdeepcollection-iteratee_identity"></a>`_.flatMapDeep(collection, [iteratee=_.identity])`
+<a href="#_flatmapdeepcollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8430 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flatmapdeep "See the npm package")
+
+This method is like `_.flatMap` except that it recursively flattens the
+mapped results.
+
+#### Since
+4.7.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the new flattened array.
+
+#### Example
+```js
+function duplicate(n) {
+  return [[[n, n]]];
+}
+
+_.flatMapDeep([1, 2], duplicate);
+// => [1, 1, 2, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flatmapdepthcollection-iteratee_identity-depth1"></a>`_.flatMapDepth(collection, [iteratee=_.identity], [depth=1])`
+<a href="#_flatmapdepthcollection-iteratee_identity-depth1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8456 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flatmapdepth "See the npm package")
+
+This method is like `_.flatMap` except that it recursively flattens the
+mapped results up to `depth` times.
+
+#### Since
+4.7.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+3. `[depth=1]` *(number)*: The maximum recursion depth.
+
+#### Returns
+*(Array)*: Returns the new flattened array.
+
+#### Example
+```js
+function duplicate(n) {
+  return [[[n, n]]];
+}
+
+_.flatMapDepth([1, 2], duplicate, 2);
+// => [[1, 1], [2, 2]]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_foreachcollection-iteratee_identity"></a>`_.forEach(collection, [iteratee=_.identity])`
+<a href="#_foreachcollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8491 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.foreach "See the npm package")
+
+Iterates over elements of `collection` and invokes `iteratee` for each element.
+The iteratee is invoked with three arguments: *(value, index|key, collection)*.
+Iteratee functions may exit iteration early by explicitly returning `false`.
+<br>
+<br>
+**Note:** As with other "Collections" methods, objects with a "length"
+property are iterated like arrays. To avoid this behavior use `_.forIn`
+or `_.forOwn` for object iteration.
+
+#### Since
+0.1.0
+#### Aliases
+*_.each*
+
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(&#42;)*: Returns `collection`.
+
+#### Example
+```js
+_([1, 2]).forEach(function(value) {
+  console.log(value);
+});
+// => Logs `1` then `2`.
+
+_.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+  console.log(key);
+});
+// => Logs 'a' then 'b' (iteration order is not guaranteed).
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_foreachrightcollection-iteratee_identity"></a>`_.forEachRight(collection, [iteratee=_.identity])`
+<a href="#_foreachrightcollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8517 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.foreachright "See the npm package")
+
+This method is like `_.forEach` except that it iterates over elements of
+`collection` from right to left.
+
+#### Since
+2.0.0
+#### Aliases
+*_.eachRight*
+
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(&#42;)*: Returns `collection`.
+
+#### Example
+```js
+_.forEachRight([1, 2], function(value) {
+  console.log(value);
+});
+// => Logs `2` then `1`.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_groupbycollection-iteratee_identity"></a>`_.groupBy(collection, [iteratee=_.identity])`
+<a href="#_groupbycollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8547 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.groupby "See the npm package")
+
+Creates an object composed of keys generated from the results of running
+each element of `collection` thru `iteratee`. The order of grouped values
+is determined by the order they occur in `collection`. The corresponding
+value of each key is an array of elements responsible for generating the
+key. The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee to transform keys.
+
+#### Returns
+*(Object)*: Returns the composed aggregate object.
+
+#### Example
+```js
+_.groupBy([6.1, 4.2, 6.3], Math.floor);
+// => { '4': [4.2], '6': [6.1, 6.3] }
+
+// The `_.property` iteratee shorthand.
+_.groupBy(['one', 'two', 'three'], 'length');
+// => { '3': ['one', 'two'], '5': ['three'] }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_includescollection-value-fromindex0"></a>`_.includes(collection, value, [fromIndex=0])`
+<a href="#_includescollection-value-fromindex0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8585 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.includes "See the npm package")
+
+Checks if `value` is in `collection`. If `collection` is a string, it's
+checked for a substring of `value`, otherwise
+[`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+is used for equality comparisons. If `fromIndex` is negative, it's used as
+the offset from the end of `collection`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object|string)*: The collection to search.
+2. `value` *(&#42;)*: The value to search for.
+3. `[fromIndex=0]` *(number)*: The index to search from.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is found, else `false`.
+
+#### Example
+```js
+_.includes([1, 2, 3], 1);
+// => true
+
+_.includes([1, 2, 3], 1, 2);
+// => false
+
+_.includes({ 'user': 'fred', 'age': 40 }, 'fred');
+// => true
+
+_.includes('pebbles', 'eb');
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_invokemapcollection-path-args"></a>`_.invokeMap(collection, path, [args])`
+<a href="#_invokemapcollection-path-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8621 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.invokemap "See the npm package")
+
+Invokes the method at `path` of each element in `collection`, returning
+an array of the results of each invoked method. Any additional arguments
+are provided to each invoked method. If `methodName` is a function, it's
+invoked for and `this` bound to, each element in `collection`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `path` *(Array|Function|string)*: The path of the method to invoke or the function invoked per iteration.
+3. `[args]` *(...&#42;)*: The arguments to invoke each method with.
+
+#### Returns
+*(Array)*: Returns the array of results.
+
+#### Example
+```js
+_.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+// => [[1, 5, 7], [1, 2, 3]]
+
+_.invokeMap([123, 456], String.prototype.split, '');
+// => [['1', '2', '3'], ['4', '5', '6']]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_keybycollection-iteratee_identity"></a>`_.keyBy(collection, [iteratee=_.identity])`
+<a href="#_keybycollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8663 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.keyby "See the npm package")
+
+Creates an object composed of keys generated from the results of running
+each element of `collection` thru `iteratee`. The corresponding value of
+each key is the last element responsible for generating the key. The
+iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee to transform keys.
+
+#### Returns
+*(Object)*: Returns the composed aggregate object.
+
+#### Example
+```js
+var array = [
+  { 'dir': 'left', 'code': 97 },
+  { 'dir': 'right', 'code': 100 }
+];
+
+_.keyBy(array, function(o) {
+  return String.fromCharCode(o.code);
+});
+// => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+
+_.keyBy(array, 'dir');
+// => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_mapcollection-iteratee_identity"></a>`_.map(collection, [iteratee=_.identity])`
+<a href="#_mapcollection-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8710 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.map "See the npm package")
+
+Creates an array of values by running each element in `collection` thru
+`iteratee`. The iteratee is invoked with three arguments:<br>
+*(value, index|key, collection)*.
+<br>
+<br>
+Many lodash methods are guarded to work as iteratees for methods like
+`_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+<br>
+<br>
+The guarded methods are:<br>
+`ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+`fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+`sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+`template`, `trim`, `trimEnd`, `trimStart`, and `words`
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the new mapped array.
+
+#### Example
+```js
+function square(n) {
+  return n * n;
+}
+
+_.map([4, 8], square);
+// => [16, 64]
+
+_.map({ 'a': 4, 'b': 8 }, square);
+// => [16, 64] (iteration order is not guaranteed)
+
+var users = [
+  { 'user': 'barney' },
+  { 'user': 'fred' }
+];
+
+// The `_.property` iteratee shorthand.
+_.map(users, 'user');
+// => ['barney', 'fred']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_orderbycollection-iteratees_identity-orders"></a>`_.orderBy(collection, [iteratees=[_.identity]], [orders])`
+<a href="#_orderbycollection-iteratees_identity-orders">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8744 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.orderby "See the npm package")
+
+This method is like `_.sortBy` except that it allows specifying the sort
+orders of the iteratees to sort by. If `orders` is unspecified, all values
+are sorted in ascending order. Otherwise, specify an order of "desc" for
+descending or "asc" for ascending sort order of corresponding values.
+
+#### Since
+4.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratees=[_.identity]]` *(Array&#91;&#93;|Function&#91;&#93;|Object&#91;&#93;|string&#91;&#93;)*: The iteratees to sort by.
+3. `[orders]` *(string&#91;&#93;)*: The sort orders of `iteratees`.
+
+#### Returns
+*(Array)*: Returns the new sorted array.
+
+#### Example
+```js
+var users = [
+  { 'user': 'fred',   'age': 48 },
+  { 'user': 'barney', 'age': 34 },
+  { 'user': 'fred',   'age': 40 },
+  { 'user': 'barney', 'age': 36 }
+];
+
+// Sort by `user` in ascending order and by `age` in descending order.
+_.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_partitioncollection-predicate_identity"></a>`_.partition(collection, [predicate=_.identity])`
+<a href="#_partitioncollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8795 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.partition "See the npm package")
+
+Creates an array of elements split into two groups, the first of which
+contains elements `predicate` returns truthy for, the second of which
+contains elements `predicate` returns falsey for. The predicate is
+invoked with one argument: *(value)*.
+
+#### Since
+3.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the array of grouped elements.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'age': 36, 'active': false },
+  { 'user': 'fred',    'age': 40, 'active': true },
+  { 'user': 'pebbles', 'age': 1,  'active': false }
+];
+
+_.partition(users, function(o) { return o.active; });
+// => objects for [['fred'], ['barney', 'pebbles']]
+
+// The `_.matches` iteratee shorthand.
+_.partition(users, { 'age': 1, 'active': false });
+// => objects for [['pebbles'], ['barney', 'fred']]
+
+// The `_.matchesProperty` iteratee shorthand.
+_.partition(users, ['active', false]);
+// => objects for [['barney', 'pebbles'], ['fred']]
+
+// The `_.property` iteratee shorthand.
+_.partition(users, 'active');
+// => objects for [['fred'], ['barney', 'pebbles']]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_reducecollection-iteratee_identity-accumulator"></a>`_.reduce(collection, [iteratee=_.identity], [accumulator])`
+<a href="#_reducecollection-iteratee_identity-accumulator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8836 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.reduce "See the npm package")
+
+Reduces `collection` to a value which is the accumulated result of running
+each element in `collection` thru `iteratee`, where each successive
+invocation is supplied the return value of the previous. If `accumulator`
+is not given, the first element of `collection` is used as the initial
+value. The iteratee is invoked with four arguments:<br>
+*(accumulator, value, index|key, collection)*.
+<br>
+<br>
+Many lodash methods are guarded to work as iteratees for methods like
+`_.reduce`, `_.reduceRight`, and `_.transform`.
+<br>
+<br>
+The guarded methods are:<br>
+`assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+and `sortBy`
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+3. `[accumulator]` *(&#42;)*: The initial value.
+
+#### Returns
+*(&#42;)*: Returns the accumulated value.
+
+#### Example
+```js
+_.reduce([1, 2], function(sum, n) {
+  return sum + n;
+}, 0);
+// => 3
+
+_.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+  (result[value] || (result[value] = [])).push(key);
+  return result;
+}, {});
+// => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_reducerightcollection-iteratee_identity-accumulator"></a>`_.reduceRight(collection, [iteratee=_.identity], [accumulator])`
+<a href="#_reducerightcollection-iteratee_identity-accumulator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8865 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.reduceright "See the npm package")
+
+This method is like `_.reduce` except that it iterates over elements of
+`collection` from right to left.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+3. `[accumulator]` *(&#42;)*: The initial value.
+
+#### Returns
+*(&#42;)*: Returns the accumulated value.
+
+#### Example
+```js
+var array = [[0, 1], [2, 3], [4, 5]];
+
+_.reduceRight(array, function(flattened, other) {
+  return flattened.concat(other);
+}, []);
+// => [4, 5, 2, 3, 0, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_rejectcollection-predicate_identity"></a>`_.reject(collection, [predicate=_.identity])`
+<a href="#_rejectcollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8907 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.reject "See the npm package")
+
+The opposite of `_.filter`; this method returns the elements of `collection`
+that `predicate` does **not** return truthy for.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the new filtered array.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney', 'age': 36, 'active': false },
+  { 'user': 'fred',   'age': 40, 'active': true }
+];
+
+_.reject(users, function(o) { return !o.active; });
+// => objects for ['fred']
+
+// The `_.matches` iteratee shorthand.
+_.reject(users, { 'age': 40, 'active': true });
+// => objects for ['barney']
+
+// The `_.matchesProperty` iteratee shorthand.
+_.reject(users, ['active', false]);
+// => objects for ['fred']
+
+// The `_.property` iteratee shorthand.
+_.reject(users, 'active');
+// => objects for ['barney']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_samplecollection"></a>`_.sample(collection)`
+<a href="#_samplecollection">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8929 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sample "See the npm package")
+
+Gets a random element from `collection`.
+
+#### Since
+2.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to sample.
+
+#### Returns
+*(&#42;)*: Returns the random element.
+
+#### Example
+```js
+_.sample([1, 2, 3, 4]);
+// => 2
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_samplesizecollection-n1"></a>`_.sampleSize(collection, [n=1])`
+<a href="#_samplesizecollection-n1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8956 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.samplesize "See the npm package")
+
+Gets `n` random elements at unique keys from `collection` up to the
+size of `collection`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to sample.
+2. `[n=1]` *(number)*: The number of elements to sample.
+
+#### Returns
+*(Array)*: Returns the random elements.
+
+#### Example
+```js
+_.sampleSize([1, 2, 3], 2);
+// => [3, 1]
+
+_.sampleSize([1, 2, 3], 4);
+// => [2, 3, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_shufflecollection"></a>`_.shuffle(collection)`
+<a href="#_shufflecollection">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8993 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.shuffle "See the npm package")
+
+Creates an array of shuffled values, using a version of the
+[Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to shuffle.
+
+#### Returns
+*(Array)*: Returns the new shuffled array.
+
+#### Example
+```js
+_.shuffle([1, 2, 3, 4]);
+// => [4, 1, 3, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sizecollection"></a>`_.size(collection)`
+<a href="#_sizecollection">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9018 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.size "See the npm package")
+
+Gets the size of `collection` by returning its length for array-like
+values or the number of own enumerable string keyed properties for objects.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to inspect.
+
+#### Returns
+*(number)*: Returns the collection size.
+
+#### Example
+```js
+_.size([1, 2, 3]);
+// => 3
+
+_.size({ 'a': 1, 'b': 2 });
+// => 2
+
+_.size('pebbles');
+// => 7
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_somecollection-predicate_identity"></a>`_.some(collection, [predicate=_.identity])`
+<a href="#_somecollection-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9072 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.some "See the npm package")
+
+Checks if `predicate` returns truthy for **any** element of `collection`.
+Iteration is stopped once `predicate` returns truthy. The predicate is
+invoked with three arguments: *(value, index|key, collection)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(boolean)*: Returns `true` if any element passes the predicate check, else `false`.
+
+#### Example
+```js
+_.some([null, 0, 'yes', false], Boolean);
+// => true
+
+var users = [
+  { 'user': 'barney', 'active': true },
+  { 'user': 'fred',   'active': false }
+];
+
+// The `_.matches` iteratee shorthand.
+_.some(users, { 'user': 'barney', 'active': false });
+// => false
+
+// The `_.matchesProperty` iteratee shorthand.
+_.some(users, ['active', false]);
+// => true
+
+// The `_.property` iteratee shorthand.
+_.some(users, 'active');
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sortbycollection-iteratees_identity"></a>`_.sortBy(collection, [iteratees=[_.identity]])`
+<a href="#_sortbycollection-iteratees_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9114 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sortby "See the npm package")
+
+Creates an array of elements, sorted in ascending order by the results of
+running each element in a collection thru each iteratee. This method
+performs a stable sort, that is, it preserves the original sort order of
+equal elements. The iteratees are invoked with one argument: *(value)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `collection` *(Array|Object)*: The collection to iterate over.
+2. `[iteratees=[_.identity]]` *(...(Array|Array&#91;&#93;|Function|Function&#91;&#93;|Object|Object&#91;&#93;|string|string&#91;&#93;))*: The iteratees to sort by.
+
+#### Returns
+*(Array)*: Returns the new sorted array.
+
+#### Example
+```js
+var users = [
+  { 'user': 'fred',   'age': 48 },
+  { 'user': 'barney', 'age': 36 },
+  { 'user': 'fred',   'age': 40 },
+  { 'user': 'barney', 'age': 34 }
+];
+
+_.sortBy(users, function(o) { return o.user; });
+// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+
+_.sortBy(users, ['user', 'age']);
+// => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+
+_.sortBy(users, 'user', function(o) {
+  return Math.floor(o.age / 10);
+});
+// => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Date” Methods`
+
+<!-- div -->
+
+### <a id="_now"></a>`_.now()`
+<a href="#_now">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9150 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.now "See the npm package")
+
+Gets the timestamp of the number of milliseconds that have elapsed since
+the Unix epoch *(1 January `1970 00`:00:00 UTC)*.
+
+#### Since
+2.4.0
+#### Returns
+*(number)*: Returns the timestamp.
+
+#### Example
+```js
+_.defer(function(stamp) {
+  console.log(_.now() - stamp);
+}, _.now());
+// => Logs the number of milliseconds it took for the deferred function to be invoked.
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Function” Methods`
+
+<!-- div -->
+
+### <a id="_aftern-func"></a>`_.after(n, func)`
+<a href="#_aftern-func">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9178 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.after "See the npm package")
+
+The opposite of `_.before`; this method creates a function that invokes
+`func` once it's called `n` or more times.
+
+#### Since
+0.1.0
+#### Arguments
+1. `n` *(number)*: The number of calls before `func` is invoked.
+2. `func` *(Function)*: The function to restrict.
+
+#### Returns
+*(Function)*: Returns the new restricted function.
+
+#### Example
+```js
+var saves = ['profile', 'settings'];
+
+var done = _.after(saves.length, function() {
+  console.log('done saving!');
+});
+
+_.forEach(saves, function(type) {
+  asyncSave({ 'type': type, 'complete': done });
+});
+// => Logs 'done saving!' after the two async saves have completed.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_aryfunc-nfunclength"></a>`_.ary(func, [n=func.length])`
+<a href="#_aryfunc-nfunclength">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9207 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.ary "See the npm package")
+
+Creates a function that invokes `func`, with up to `n` arguments,
+ignoring any additional arguments.
+
+#### Since
+3.0.0
+#### Arguments
+1. `func` *(Function)*: The function to cap arguments for.
+2. `[n=func.length]` *(number)*: The arity cap.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+_.map(['6', '8', '10'], _.ary(parseInt, 1));
+// => [6, 8, 10]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_beforen-func"></a>`_.before(n, func)`
+<a href="#_beforen-func">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9230 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.before "See the npm package")
+
+Creates a function that invokes `func`, with the `this` binding and arguments
+of the created function, while it's called less than `n` times. Subsequent
+calls to the created function return the result of the last `func` invocation.
+
+#### Since
+3.0.0
+#### Arguments
+1. `n` *(number)*: The number of calls at which `func` is no longer invoked.
+2. `func` *(Function)*: The function to restrict.
+
+#### Returns
+*(Function)*: Returns the new restricted function.
+
+#### Example
+```js
+jQuery(element).on('click', _.before(5, addContactToList));
+// => allows adding up to 4 contacts to the list
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_bindfunc-thisarg-partials"></a>`_.bind(func, thisArg, [partials])`
+<a href="#_bindfunc-thisarg-partials">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9282 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.bind "See the npm package")
+
+Creates a function that invokes `func` with the `this` binding of `thisArg`
+and `partials` prepended to the arguments it receives.
+<br>
+<br>
+The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+may be used as a placeholder for partially applied arguments.
+<br>
+<br>
+**Note:** Unlike native `Function#bind` this method doesn't set the "length"
+property of bound functions.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to bind.
+2. `thisArg` *(&#42;)*: The `this` binding of `func`.
+3. `[partials]` *(...&#42;)*: The arguments to be partially applied.
+
+#### Returns
+*(Function)*: Returns the new bound function.
+
+#### Example
+```js
+var greet = function(greeting, punctuation) {
+  return greeting + ' ' + this.user + punctuation;
+};
+
+var object = { 'user': 'fred' };
+
+var bound = _.bind(greet, object, 'hi');
+bound('!');
+// => 'hi fred!'
+
+// Bound with placeholders.
+var bound = _.bind(greet, object, _, '!');
+bound('hi');
+// => 'hi fred!'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_bindkeyobject-key-partials"></a>`_.bindKey(object, key, [partials])`
+<a href="#_bindkeyobject-key-partials">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9336 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.bindkey "See the npm package")
+
+Creates a function that invokes the method at `object[key]` with `partials`
+prepended to the arguments it receives.
+<br>
+<br>
+This method differs from `_.bind` by allowing bound functions to reference
+methods that may be redefined or don't yet exist. See
+[Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+for more details.
+<br>
+<br>
+The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+builds, may be used as a placeholder for partially applied arguments.
+
+#### Since
+0.10.0
+#### Arguments
+1. `object` *(Object)*: The object to invoke the method on.
+2. `key` *(string)*: The key of the method.
+3. `[partials]` *(...&#42;)*: The arguments to be partially applied.
+
+#### Returns
+*(Function)*: Returns the new bound function.
+
+#### Example
+```js
+var object = {
+  'user': 'fred',
+  'greet': function(greeting, punctuation) {
+    return greeting + ' ' + this.user + punctuation;
+  }
+};
+
+var bound = _.bindKey(object, 'greet', 'hi');
+bound('!');
+// => 'hi fred!'
+
+object.greet = function(greeting, punctuation) {
+  return greeting + 'ya ' + this.user + punctuation;
+};
+
+bound('!');
+// => 'hiya fred!'
+
+// Bound with placeholders.
+var bound = _.bindKey(object, 'greet', _, '!');
+bound('hi');
+// => 'hiya fred!'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_curryfunc-arityfunclength"></a>`_.curry(func, [arity=func.length])`
+<a href="#_curryfunc-arityfunclength">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9386 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.curry "See the npm package")
+
+Creates a function that accepts arguments of `func` and either invokes
+`func` returning its result, if at least `arity` number of arguments have
+been provided, or returns a function that accepts the remaining `func`
+arguments, and so on. The arity of `func` may be specified if `func.length`
+is not sufficient.
+<br>
+<br>
+The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+may be used as a placeholder for provided arguments.
+<br>
+<br>
+**Note:** This method doesn't set the "length" property of curried functions.
+
+#### Since
+2.0.0
+#### Arguments
+1. `func` *(Function)*: The function to curry.
+2. `[arity=func.length]` *(number)*: The arity of `func`.
+
+#### Returns
+*(Function)*: Returns the new curried function.
+
+#### Example
+```js
+var abc = function(a, b, c) {
+  return [a, b, c];
+};
+
+var curried = _.curry(abc);
+
+curried(1)(2)(3);
+// => [1, 2, 3]
+
+curried(1, 2)(3);
+// => [1, 2, 3]
+
+curried(1, 2, 3);
+// => [1, 2, 3]
+
+// Curried with placeholders.
+curried(1)(_, 3)(2);
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_curryrightfunc-arityfunclength"></a>`_.curryRight(func, [arity=func.length])`
+<a href="#_curryrightfunc-arityfunclength">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9431 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.curryright "See the npm package")
+
+This method is like `_.curry` except that arguments are applied to `func`
+in the manner of `_.partialRight` instead of `_.partial`.
+<br>
+<br>
+The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+builds, may be used as a placeholder for provided arguments.
+<br>
+<br>
+**Note:** This method doesn't set the "length" property of curried functions.
+
+#### Since
+3.0.0
+#### Arguments
+1. `func` *(Function)*: The function to curry.
+2. `[arity=func.length]` *(number)*: The arity of `func`.
+
+#### Returns
+*(Function)*: Returns the new curried function.
+
+#### Example
+```js
+var abc = function(a, b, c) {
+  return [a, b, c];
+};
+
+var curried = _.curryRight(abc);
+
+curried(3)(2)(1);
+// => [1, 2, 3]
+
+curried(2, 3)(1);
+// => [1, 2, 3]
+
+curried(1, 2, 3);
+// => [1, 2, 3]
+
+// Curried with placeholders.
+curried(3)(1, _)(2);
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_debouncefunc-wait0-options-optionsleadingfalse-optionsmaxwait-optionstrailingtrue"></a>`_.debounce(func, [wait=0], [options={}], [options.leading=false], [options.maxWait], [options.trailing=true])`
+<a href="#_debouncefunc-wait0-options-optionsleadingfalse-optionsmaxwait-optionstrailingtrue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9488 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.debounce "See the npm package")
+
+Creates a debounced function that delays invoking `func` until after `wait`
+milliseconds have elapsed since the last time the debounced function was
+invoked. The debounced function comes with a `cancel` method to cancel
+delayed `func` invocations and a `flush` method to immediately invoke them.
+Provide an options object to indicate whether `func` should be invoked on
+the leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+with the last arguments provided to the debounced function. Subsequent calls
+to the debounced function return the result of the last `func` invocation.
+<br>
+<br>
+**Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+on the trailing edge of the timeout only if the debounced function is
+invoked more than once during the `wait` timeout.
+<br>
+<br>
+See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+for details over the differences between `_.debounce` and `_.throttle`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to debounce.
+2. `[wait=0]` *(number)*: The number of milliseconds to delay.
+3. `[options={}]` *(Object)*: The options object.
+4. `[options.leading=false]` *(boolean)*: Specify invoking on the leading edge of the timeout.
+5. `[options.maxWait]` *(number)*: The maximum time `func` is allowed to be delayed before it's invoked.
+6. `[options.trailing=true]` *(boolean)*: Specify invoking on the trailing edge of the timeout.
+
+#### Returns
+*(Function)*: Returns the new debounced function.
+
+#### Example
+```js
+// Avoid costly calculations while the window size is in flux.
+jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+
+// Invoke `sendMail` when clicked, debouncing subsequent calls.
+jQuery(element).on('click', _.debounce(sendMail, 300, {
+  'leading': true,
+  'trailing': false
+}));
+
+// Ensure `batchLog` is invoked once after 1 second of debounced calls.
+var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+var source = new EventSource('/stream');
+jQuery(source).on('message', debounced);
+
+// Cancel the trailing debounced invocation.
+jQuery(window).on('popstate', debounced.cancel);
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_deferfunc-args"></a>`_.defer(func, [args])`
+<a href="#_deferfunc-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9630 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.defer "See the npm package")
+
+Defers invoking the `func` until the current call stack has cleared. Any
+additional arguments are provided to `func` when it's invoked.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to defer.
+2. `[args]` *(...&#42;)*: The arguments to invoke `func` with.
+
+#### Returns
+*(number)*: Returns the timer id.
+
+#### Example
+```js
+_.defer(function(text) {
+  console.log(text);
+}, 'deferred');
+// => Logs 'deferred' after one or more milliseconds.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_delayfunc-wait-args"></a>`_.delay(func, wait, [args])`
+<a href="#_delayfunc-wait-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9653 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.delay "See the npm package")
+
+Invokes `func` after `wait` milliseconds. Any additional arguments are
+provided to `func` when it's invoked.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to delay.
+2. `wait` *(number)*: The number of milliseconds to delay invocation.
+3. `[args]` *(...&#42;)*: The arguments to invoke `func` with.
+
+#### Returns
+*(number)*: Returns the timer id.
+
+#### Example
+```js
+_.delay(function(text) {
+  console.log(text);
+}, 1000, 'later');
+// => Logs 'later' after one second.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flipfunc"></a>`_.flip(func)`
+<a href="#_flipfunc">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9675 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flip "See the npm package")
+
+Creates a function that invokes `func` with arguments reversed.
+
+#### Since
+4.0.0
+#### Arguments
+1. `func` *(Function)*: The function to flip arguments for.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var flipped = _.flip(function() {
+  return _.toArray(arguments);
+});
+
+flipped('a', 'b', 'c', 'd');
+// => ['d', 'c', 'b', 'a']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_memoizefunc-resolver"></a>`_.memoize(func, [resolver])`
+<a href="#_memoizefunc-resolver">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9723 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.memoize "See the npm package")
+
+Creates a function that memoizes the result of `func`. If `resolver` is
+provided, it determines the cache key for storing the result based on the
+arguments provided to the memoized function. By default, the first argument
+provided to the memoized function is used as the map cache key. The `func`
+is invoked with the `this` binding of the memoized function.
+<br>
+<br>
+**Note:** The cache is exposed as the `cache` property on the memoized
+function. Its creation may be customized by replacing the `_.memoize.Cache`
+constructor with one whose instances implement the
+[`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
+method interface of `delete`, `get`, `has`, and `set`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to have its output memoized.
+2. `[resolver]` *(Function)*: The function to resolve the cache key.
+
+#### Returns
+*(Function)*: Returns the new memoizing function.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': 2 };
+var other = { 'c': 3, 'd': 4 };
+
+var values = _.memoize(_.values);
+values(object);
+// => [1, 2]
+
+values(other);
+// => [3, 4]
+
+object.a = 2;
+values(object);
+// => [1, 2]
+
+// Modify the result cache.
+values.cache.set(object, ['a', 'b']);
+values(object);
+// => ['a', 'b']
+
+// Replace `_.memoize.Cache`.
+_.memoize.Cache = WeakMap;
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_negatepredicate"></a>`_.negate(predicate)`
+<a href="#_negatepredicate">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9766 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.negate "See the npm package")
+
+Creates a function that negates the result of the predicate `func`. The
+`func` predicate is invoked with the `this` binding and arguments of the
+created function.
+
+#### Since
+3.0.0
+#### Arguments
+1. `predicate` *(Function)*: The predicate to negate.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+function isEven(n) {
+  return n % 2 == 0;
+}
+
+_.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+// => [1, 3, 5]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_oncefunc"></a>`_.once(func)`
+<a href="#_oncefunc">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9793 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.once "See the npm package")
+
+Creates a function that is restricted to invoking `func` once. Repeat calls
+to the function return the value of the first invocation. The `func` is
+invoked with the `this` binding and arguments of the created function.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to restrict.
+
+#### Returns
+*(Function)*: Returns the new restricted function.
+
+#### Example
+```js
+var initialize = _.once(createApplication);
+initialize();
+initialize();
+// `initialize` invokes `createApplication` once
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_overargsfunc"></a>`_.overArgs(func)`
+<a href="#_overargsfunc">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9829 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.overargs "See the npm package")
+
+Creates a function that invokes `func` with arguments transformed by
+corresponding `transforms`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `func` *(Function)*: The function to wrap.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+function doubled(n) {
+  return n * 2;
+}
+
+function square(n) {
+  return n * n;
+}
+
+var func = _.overArgs(function(x, y) {
+  return [x, y];
+}, square, doubled);
+
+func(9, 3);
+// => [81, 6]
+
+func(10, 5);
+// => [100, 10]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_partialfunc-partials"></a>`_.partial(func, [partials])`
+<a href="#_partialfunc-partials">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9879 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.partial "See the npm package")
+
+Creates a function that invokes `func` with `partials` prepended to the
+arguments it receives. This method is like `_.bind` except it does **not**
+alter the `this` binding.
+<br>
+<br>
+The `_.partial.placeholder` value, which defaults to `_` in monolithic
+builds, may be used as a placeholder for partially applied arguments.
+<br>
+<br>
+**Note:** This method doesn't set the "length" property of partially
+applied functions.
+
+#### Since
+0.2.0
+#### Arguments
+1. `func` *(Function)*: The function to partially apply arguments to.
+2. `[partials]` *(...&#42;)*: The arguments to be partially applied.
+
+#### Returns
+*(Function)*: Returns the new partially applied function.
+
+#### Example
+```js
+var greet = function(greeting, name) {
+  return greeting + ' ' + name;
+};
+
+var sayHelloTo = _.partial(greet, 'hello');
+sayHelloTo('fred');
+// => 'hello fred'
+
+// Partially applied with placeholders.
+var greetFred = _.partial(greet, _, 'fred');
+greetFred('hi');
+// => 'hi fred'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_partialrightfunc-partials"></a>`_.partialRight(func, [partials])`
+<a href="#_partialrightfunc-partials">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9916 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.partialright "See the npm package")
+
+This method is like `_.partial` except that partially applied arguments
+are appended to the arguments it receives.
+<br>
+<br>
+The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+builds, may be used as a placeholder for partially applied arguments.
+<br>
+<br>
+**Note:** This method doesn't set the "length" property of partially
+applied functions.
+
+#### Since
+1.0.0
+#### Arguments
+1. `func` *(Function)*: The function to partially apply arguments to.
+2. `[partials]` *(...&#42;)*: The arguments to be partially applied.
+
+#### Returns
+*(Function)*: Returns the new partially applied function.
+
+#### Example
+```js
+var greet = function(greeting, name) {
+  return greeting + ' ' + name;
+};
+
+var greetFred = _.partialRight(greet, 'fred');
+greetFred('hi');
+// => 'hi fred'
+
+// Partially applied with placeholders.
+var sayHelloTo = _.partialRight(greet, 'hello', _);
+sayHelloTo('fred');
+// => 'hello fred'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_reargfunc-indexes"></a>`_.rearg(func, indexes)`
+<a href="#_reargfunc-indexes">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9943 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.rearg "See the npm package")
+
+Creates a function that invokes `func` with arguments arranged according
+to the specified `indexes` where the argument value at the first index is
+provided as the first argument, the argument value at the second index is
+provided as the second argument, and so on.
+
+#### Since
+3.0.0
+#### Arguments
+1. `func` *(Function)*: The function to rearrange arguments for.
+2. `indexes` *(...(number|number&#91;&#93;))*: The arranged argument indexes.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var rearged = _.rearg(function(a, b, c) {
+  return [a, b, c];
+}, 2, 0, 1);
+
+rearged('b', 'c', 'a')
+// => ['a', 'b', 'c']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_restfunc-startfunclength-1"></a>`_.rest(func, [start=func.length-1])`
+<a href="#_restfunc-startfunclength-1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L9972 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.rest "See the npm package")
+
+Creates a function that invokes `func` with the `this` binding of the
+created function and arguments from `start` and beyond provided as
+an array.
+<br>
+<br>
+**Note:** This method is based on the
+[rest parameter](https://mdn.io/rest_parameters).
+
+#### Since
+4.0.0
+#### Arguments
+1. `func` *(Function)*: The function to apply a rest parameter to.
+2. `[start=func.length-1]` *(number)*: The start position of the rest parameter.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var say = _.rest(function(what, names) {
+  return what + ' ' + _.initial(names).join(', ') +
+    (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+});
+
+say('hello', 'fred', 'barney', 'pebbles');
+// => 'hello fred, barney, & pebbles'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_spreadfunc-start0"></a>`_.spread(func, [start=0])`
+<a href="#_spreadfunc-start0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10035 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.spread "See the npm package")
+
+Creates a function that invokes `func` with the `this` binding of the
+create function and an array of arguments much like
+[`Function#apply`](http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply).
+<br>
+<br>
+**Note:** This method is based on the
+[spread operator](https://mdn.io/spread_operator).
+
+#### Since
+3.2.0
+#### Arguments
+1. `func` *(Function)*: The function to spread arguments over.
+2. `[start=0]` *(number)*: The start position of the spread.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var say = _.spread(function(who, what) {
+  return who + ' says ' + what;
+});
+
+say(['fred', 'hello']);
+// => 'fred says hello'
+
+var numbers = Promise.all([
+  Promise.resolve(40),
+  Promise.resolve(36)
+]);
+
+numbers.then(_.spread(function(x, y) {
+  return x + y;
+}));
+// => a Promise of 76
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_throttlefunc-wait0-options-optionsleadingtrue-optionstrailingtrue"></a>`_.throttle(func, [wait=0], [options={}], [options.leading=true], [options.trailing=true])`
+<a href="#_throttlefunc-wait0-options-optionsleadingtrue-optionstrailingtrue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10092 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.throttle "See the npm package")
+
+Creates a throttled function that only invokes `func` at most once per
+every `wait` milliseconds. The throttled function comes with a `cancel`
+method to cancel delayed `func` invocations and a `flush` method to
+immediately invoke them. Provide an options object to indicate whether
+`func` should be invoked on the leading and/or trailing edge of the `wait`
+timeout. The `func` is invoked with the last arguments provided to the
+throttled function. Subsequent calls to the throttled function return the
+result of the last `func` invocation.
+<br>
+<br>
+**Note:** If `leading` and `trailing` options are `true`, `func` is
+invoked on the trailing edge of the timeout only if the throttled function
+is invoked more than once during the `wait` timeout.
+<br>
+<br>
+See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+for details over the differences between `_.throttle` and `_.debounce`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `func` *(Function)*: The function to throttle.
+2. `[wait=0]` *(number)*: The number of milliseconds to throttle invocations to.
+3. `[options={}]` *(Object)*: The options object.
+4. `[options.leading=true]` *(boolean)*: Specify invoking on the leading edge of the timeout.
+5. `[options.trailing=true]` *(boolean)*: Specify invoking on the trailing edge of the timeout.
+
+#### Returns
+*(Function)*: Returns the new throttled function.
+
+#### Example
+```js
+// Avoid excessively updating the position while scrolling.
+jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+
+// Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+jQuery(element).on('click', throttled);
+
+// Cancel the trailing throttled invocation.
+jQuery(window).on('popstate', throttled.cancel);
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unaryfunc"></a>`_.unary(func)`
+<a href="#_unaryfunc">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10125 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unary "See the npm package")
+
+Creates a function that accepts up to one argument, ignoring any
+additional arguments.
+
+#### Since
+4.0.0
+#### Arguments
+1. `func` *(Function)*: The function to cap arguments for.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+_.map(['6', '8', '10'], _.unary(parseInt));
+// => [6, 8, 10]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_wrapvalue-wrapperidentity"></a>`_.wrap(value, [wrapper=identity])`
+<a href="#_wrapvalue-wrapperidentity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10151 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.wrap "See the npm package")
+
+Creates a function that provides `value` to the wrapper function as its
+first argument. Any additional arguments provided to the function are
+appended to those provided to the wrapper function. The wrapper is invoked
+with the `this` binding of the created function.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to wrap.
+2. `[wrapper=identity]` *(Function)*: The wrapper function.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var p = _.wrap(_.escape, function(func, text) {
+  return '<p>' + func(text) + '</p>';
+});
+
+p('fred, barney, & pebbles');
+// => '<p>fred, barney, &amp; pebbles</p>'
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Lang” Methods`
+
+<!-- div -->
+
+### <a id="_castarrayvalue"></a>`_.castArray(value)`
+<a href="#_castarrayvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10191 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.castarray "See the npm package")
+
+Casts `value` as an array if it's not one.
+
+#### Since
+4.4.0
+#### Arguments
+1. `value` *(&#42;)*: The value to inspect.
+
+#### Returns
+*(Array)*: Returns the cast array.
+
+#### Example
+```js
+_.castArray(1);
+// => [1]
+
+_.castArray({ 'a': 1 });
+// => [{ 'a': 1 }]
+
+_.castArray('abc');
+// => ['abc']
+
+_.castArray(null);
+// => [null]
+
+_.castArray(undefined);
+// => [undefined]
+
+_.castArray();
+// => []
+
+var array = [1, 2, 3];
+console.log(_.castArray(array) === array);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_clonevalue"></a>`_.clone(value)`
+<a href="#_clonevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10225 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.clone "See the npm package")
+
+Creates a shallow clone of `value`.
+<br>
+<br>
+**Note:** This method is loosely based on the
+[structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+and supports cloning arrays, array buffers, booleans, date objects, maps,
+numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+arrays. The own enumerable properties of `arguments` objects are cloned
+as plain objects. An empty object is returned for uncloneable values such
+as error objects, functions, DOM nodes, and WeakMaps.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to clone.
+
+#### Returns
+*(&#42;)*: Returns the cloned value.
+
+#### Example
+```js
+var objects = [{ 'a': 1 }, { 'b': 2 }];
+
+var shallow = _.clone(objects);
+console.log(shallow[0] === objects[0]);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_clonedeepvalue"></a>`_.cloneDeep(value)`
+<a href="#_clonedeepvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10282 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.clonedeep "See the npm package")
+
+This method is like `_.clone` except that it recursively clones `value`.
+
+#### Since
+1.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to recursively clone.
+
+#### Returns
+*(&#42;)*: Returns the deep cloned value.
+
+#### Example
+```js
+var objects = [{ 'a': 1 }, { 'b': 2 }];
+
+var deep = _.cloneDeep(objects);
+console.log(deep[0] === objects[0]);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_clonedeepwithvalue-customizer"></a>`_.cloneDeepWith(value, [customizer])`
+<a href="#_clonedeepwithvalue-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10314 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.clonedeepwith "See the npm package")
+
+This method is like `_.cloneWith` except that it recursively clones `value`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to recursively clone.
+2. `[customizer]` *(Function)*: The function to customize cloning.
+
+#### Returns
+*(&#42;)*: Returns the deep cloned value.
+
+#### Example
+```js
+function customizer(value) {
+  if (_.isElement(value)) {
+    return value.cloneNode(true);
+  }
+}
+
+var el = _.cloneDeepWith(document.body, customizer);
+
+console.log(el === document.body);
+// => false
+console.log(el.nodeName);
+// => 'BODY'
+console.log(el.childNodes.length);
+// => 20
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_clonewithvalue-customizer"></a>`_.cloneWith(value, [customizer])`
+<a href="#_clonewithvalue-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10260 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.clonewith "See the npm package")
+
+This method is like `_.clone` except that it accepts `customizer` which
+is invoked to produce the cloned value. If `customizer` returns `undefined`,
+cloning is handled by the method instead. The `customizer` is invoked with
+up to four arguments; *(value [, index|key, object, stack])*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to clone.
+2. `[customizer]` *(Function)*: The function to customize cloning.
+
+#### Returns
+*(&#42;)*: Returns the cloned value.
+
+#### Example
+```js
+function customizer(value) {
+  if (_.isElement(value)) {
+    return value.cloneNode(false);
+  }
+}
+
+var el = _.cloneWith(document.body, customizer);
+
+console.log(el === document.body);
+// => false
+console.log(el.nodeName);
+// => 'BODY'
+console.log(el.childNodes.length);
+// => 0
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_eqvalue-other"></a>`_.eq(value, other)`
+<a href="#_eqvalue-other">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10350 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.eq "See the npm package")
+
+Performs a
+[`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+comparison between two values to determine if they are equivalent.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+
+#### Returns
+*(boolean)*: Returns `true` if the values are equivalent, else `false`.
+
+#### Example
+```js
+var object = { 'user': 'fred' };
+var other = { 'user': 'fred' };
+
+_.eq(object, object);
+// => true
+
+_.eq(object, other);
+// => false
+
+_.eq('a', 'a');
+// => true
+
+_.eq('a', Object('a'));
+// => false
+
+_.eq(NaN, NaN);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_gtvalue-other"></a>`_.gt(value, other)`
+<a href="#_gtvalue-other">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10377 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.gt "See the npm package")
+
+Checks if `value` is greater than `other`.
+
+#### Since
+3.9.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is greater than `other`, else `false`.
+
+#### Example
+```js
+_.gt(3, 1);
+// => true
+
+_.gt(3, 3);
+// => false
+
+_.gt(1, 3);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_gtevalue-other"></a>`_.gte(value, other)`
+<a href="#_gtevalue-other">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10402 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.gte "See the npm package")
+
+Checks if `value` is greater than or equal to `other`.
+
+#### Since
+3.9.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is greater than or equal to `other`, else `false`.
+
+#### Example
+```js
+_.gte(3, 1);
+// => true
+
+_.gte(3, 3);
+// => true
+
+_.gte(1, 3);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isargumentsvalue"></a>`_.isArguments(value)`
+<a href="#_isargumentsvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10424 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isarguments "See the npm package")
+
+Checks if `value` is likely an `arguments` object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isArguments(function() { return arguments; }());
+// => true
+
+_.isArguments([1, 2, 3]);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isarrayvalue"></a>`_.isArray(value)`
+<a href="#_isarrayvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10455 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isarray "See the npm package")
+
+Checks if `value` is classified as an `Array` object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isArray([1, 2, 3]);
+// => true
+
+_.isArray(document.body.children);
+// => false
+
+_.isArray('abc');
+// => false
+
+_.isArray(_.noop);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isarraybuffervalue"></a>`_.isArrayBuffer(value)`
+<a href="#_isarraybuffervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10475 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isarraybuffer "See the npm package")
+
+Checks if `value` is classified as an `ArrayBuffer` object.
+
+#### Since
+4.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isArrayBuffer(new ArrayBuffer(2));
+// => true
+
+_.isArrayBuffer(new Array(2));
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isarraylikevalue"></a>`_.isArrayLike(value)`
+<a href="#_isarraylikevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10504 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isarraylike "See the npm package")
+
+Checks if `value` is array-like. A value is considered array-like if it's
+not a function and has a `value.length` that's an integer greater than or
+equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is array-like, else `false`.
+
+#### Example
+```js
+_.isArrayLike([1, 2, 3]);
+// => true
+
+_.isArrayLike(document.body.children);
+// => true
+
+_.isArrayLike('abc');
+// => true
+
+_.isArrayLike(_.noop);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isarraylikeobjectvalue"></a>`_.isArrayLikeObject(value)`
+<a href="#_isarraylikeobjectvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10533 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isarraylikeobject "See the npm package")
+
+This method is like `_.isArrayLike` except that it also checks if `value`
+is an object.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is an array-like object, else `false`.
+
+#### Example
+```js
+_.isArrayLikeObject([1, 2, 3]);
+// => true
+
+_.isArrayLikeObject(document.body.children);
+// => true
+
+_.isArrayLikeObject('abc');
+// => false
+
+_.isArrayLikeObject(_.noop);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isbooleanvalue"></a>`_.isBoolean(value)`
+<a href="#_isbooleanvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10555 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isboolean "See the npm package")
+
+Checks if `value` is classified as a boolean primitive or object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isBoolean(false);
+// => true
+
+_.isBoolean(null);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isbuffervalue"></a>`_.isBuffer(value)`
+<a href="#_isbuffervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10577 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isbuffer "See the npm package")
+
+Checks if `value` is a buffer.
+
+#### Since
+4.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a buffer, else `false`.
+
+#### Example
+```js
+_.isBuffer(new Buffer(2));
+// => true
+
+_.isBuffer(new Uint8Array(2));
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isdatevalue"></a>`_.isDate(value)`
+<a href="#_isdatevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10599 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isdate "See the npm package")
+
+Checks if `value` is classified as a `Date` object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isDate(new Date);
+// => true
+
+_.isDate('Mon April 23 2012');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_iselementvalue"></a>`_.isElement(value)`
+<a href="#_iselementvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10621 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.iselement "See the npm package")
+
+Checks if `value` is likely a DOM element.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a DOM element, else `false`.
+
+#### Example
+```js
+_.isElement(document.body);
+// => true
+
+_.isElement('<body>');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isemptyvalue"></a>`_.isEmpty(value)`
+<a href="#_isemptyvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10658 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isempty "See the npm package")
+
+Checks if `value` is an empty object, collection, map, or set.
+<br>
+<br>
+Objects are considered empty if they have no own enumerable string keyed
+properties.
+<br>
+<br>
+Array-like values such as `arguments` objects, arrays, buffers, strings, or
+jQuery-like collections are considered empty if they have a `length` of `0`.
+Similarly, maps and sets are considered empty if they have a `size` of `0`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is empty, else `false`.
+
+#### Example
+```js
+_.isEmpty(null);
+// => true
+
+_.isEmpty(true);
+// => true
+
+_.isEmpty(1);
+// => true
+
+_.isEmpty([1, 2, 3]);
+// => false
+
+_.isEmpty({ 'a': 1 });
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isequalvalue-other"></a>`_.isEqual(value, other)`
+<a href="#_isequalvalue-other">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10707 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isequal "See the npm package")
+
+Performs a deep comparison between two values to determine if they are
+equivalent.
+<br>
+<br>
+**Note:** This method supports comparing arrays, array buffers, booleans,
+date objects, error objects, maps, numbers, `Object` objects, regexes,
+sets, strings, symbols, and typed arrays. `Object` objects are compared
+by their own, not inherited, enumerable properties. Functions and DOM
+nodes are **not** supported.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+
+#### Returns
+*(boolean)*: Returns `true` if the values are equivalent, else `false`.
+
+#### Example
+```js
+var object = { 'user': 'fred' };
+var other = { 'user': 'fred' };
+
+_.isEqual(object, other);
+// => true
+
+object === other;
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isequalwithvalue-other-customizer"></a>`_.isEqualWith(value, other, [customizer])`
+<a href="#_isequalwithvalue-other-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10744 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isequalwith "See the npm package")
+
+This method is like `_.isEqual` except that it accepts `customizer` which
+is invoked to compare values. If `customizer` returns `undefined`, comparisons
+are handled by the method instead. The `customizer` is invoked with up to
+six arguments: *(objValue, othValue [, index|key, object, other, stack])*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+3. `[customizer]` *(Function)*: The function to customize comparisons.
+
+#### Returns
+*(boolean)*: Returns `true` if the values are equivalent, else `false`.
+
+#### Example
+```js
+function isGreeting(value) {
+  return /^h(?:i|ello)$/.test(value);
+}
+
+function customizer(objValue, othValue) {
+  if (isGreeting(objValue) && isGreeting(othValue)) {
+    return true;
+  }
+}
+
+var array = ['hello', 'goodbye'];
+var other = ['hi', 'goodbye'];
+
+_.isEqualWith(array, other, customizer);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_iserrorvalue"></a>`_.isError(value)`
+<a href="#_iserrorvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10769 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.iserror "See the npm package")
+
+Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+`SyntaxError`, `TypeError`, or `URIError` object.
+
+#### Since
+3.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is an error object, else `false`.
+
+#### Example
+```js
+_.isError(new Error);
+// => true
+
+_.isError(Error);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isfinitevalue"></a>`_.isFinite(value)`
+<a href="#_isfinitevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10804 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isfinite "See the npm package")
+
+Checks if `value` is a finite primitive number.
+<br>
+<br>
+**Note:** This method is based on
+[`Number.isFinite`](https://mdn.io/Number/isFinite).
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a finite number, else `false`.
+
+#### Example
+```js
+_.isFinite(3);
+// => true
+
+_.isFinite(Number.MAX_VALUE);
+// => true
+
+_.isFinite(3.14);
+// => true
+
+_.isFinite(Infinity);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isfunctionvalue"></a>`_.isFunction(value)`
+<a href="#_isfunctionvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10826 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isfunction "See the npm package")
+
+Checks if `value` is classified as a `Function` object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isFunction(_);
+// => true
+
+_.isFunction(/abc/);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isintegervalue"></a>`_.isInteger(value)`
+<a href="#_isintegervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10860 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isinteger "See the npm package")
+
+Checks if `value` is an integer.
+<br>
+<br>
+**Note:** This method is based on
+[`Number.isInteger`](https://mdn.io/Number/isInteger).
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is an integer, else `false`.
+
+#### Example
+```js
+_.isInteger(3);
+// => true
+
+_.isInteger(Number.MIN_VALUE);
+// => false
+
+_.isInteger(Infinity);
+// => false
+
+_.isInteger('3');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_islengthvalue"></a>`_.isLength(value)`
+<a href="#_islengthvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10891 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.islength "See the npm package")
+
+Checks if `value` is a valid array-like length.
+<br>
+<br>
+**Note:** This function is loosely based on
+[`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a valid length, else `false`.
+
+#### Example
+```js
+_.isLength(3);
+// => true
+
+_.isLength(Number.MIN_VALUE);
+// => false
+
+_.isLength(Infinity);
+// => false
+
+_.isLength('3');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ismapvalue"></a>`_.isMap(value)`
+<a href="#_ismapvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10972 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.ismap "See the npm package")
+
+Checks if `value` is classified as a `Map` object.
+
+#### Since
+4.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isMap(new Map);
+// => true
+
+_.isMap(new WeakMap);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ismatchobject-source"></a>`_.isMatch(object, source)`
+<a href="#_ismatchobject-source">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11000 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.ismatch "See the npm package")
+
+Performs a partial deep comparison between `object` and `source` to
+determine if `object` contains equivalent property values. This method is
+equivalent to a `_.matches` function when `source` is partially applied.
+<br>
+<br>
+**Note:** This method supports comparing the same values as `_.isEqual`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `object` *(Object)*: The object to inspect.
+2. `source` *(Object)*: The object of property values to match.
+
+#### Returns
+*(boolean)*: Returns `true` if `object` is a match, else `false`.
+
+#### Example
+```js
+var object = { 'user': 'fred', 'age': 40 };
+
+_.isMatch(object, { 'age': 40 });
+// => true
+
+_.isMatch(object, { 'age': 36 });
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ismatchwithobject-source-customizer"></a>`_.isMatchWith(object, source, [customizer])`
+<a href="#_ismatchwithobject-source-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11036 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.ismatchwith "See the npm package")
+
+This method is like `_.isMatch` except that it accepts `customizer` which
+is invoked to compare values. If `customizer` returns `undefined`, comparisons
+are handled by the method instead. The `customizer` is invoked with five
+arguments: *(objValue, srcValue, index|key, object, source)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The object to inspect.
+2. `source` *(Object)*: The object of property values to match.
+3. `[customizer]` *(Function)*: The function to customize comparisons.
+
+#### Returns
+*(boolean)*: Returns `true` if `object` is a match, else `false`.
+
+#### Example
+```js
+function isGreeting(value) {
+  return /^h(?:i|ello)$/.test(value);
+}
+
+function customizer(objValue, srcValue) {
+  if (isGreeting(objValue) && isGreeting(srcValue)) {
+    return true;
+  }
+}
+
+var object = { 'greeting': 'hello' };
+var source = { 'greeting': 'hi' };
+
+_.isMatchWith(object, source, customizer);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isnanvalue"></a>`_.isNaN(value)`
+<a href="#_isnanvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11069 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isnan "See the npm package")
+
+Checks if `value` is `NaN`.
+<br>
+<br>
+**Note:** This method is based on
+[`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+`undefined` and other non-number values.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is `NaN`, else `false`.
+
+#### Example
+```js
+_.isNaN(NaN);
+// => true
+
+_.isNaN(new Number(NaN));
+// => true
+
+isNaN(undefined);
+// => true
+
+_.isNaN(undefined);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isnativevalue"></a>`_.isNative(value)`
+<a href="#_isnativevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11094 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isnative "See the npm package")
+
+Checks if `value` is a native function.
+
+#### Since
+3.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a native function, else `false`.
+
+#### Example
+```js
+_.isNative(Array.prototype.push);
+// => true
+
+_.isNative(_);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isnilvalue"></a>`_.isNil(value)`
+<a href="#_isnilvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11143 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isnil "See the npm package")
+
+Checks if `value` is `null` or `undefined`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is nullish, else `false`.
+
+#### Example
+```js
+_.isNil(null);
+// => true
+
+_.isNil(void 0);
+// => true
+
+_.isNil(NaN);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isnullvalue"></a>`_.isNull(value)`
+<a href="#_isnullvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11119 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isnull "See the npm package")
+
+Checks if `value` is `null`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is `null`, else `false`.
+
+#### Example
+```js
+_.isNull(null);
+// => true
+
+_.isNull(void 0);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isnumbervalue"></a>`_.isNumber(value)`
+<a href="#_isnumbervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11174 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isnumber "See the npm package")
+
+Checks if `value` is classified as a `Number` primitive or object.
+<br>
+<br>
+**Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+classified as numbers, use the `_.isFinite` method.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isNumber(3);
+// => true
+
+_.isNumber(Number.MIN_VALUE);
+// => true
+
+_.isNumber(Infinity);
+// => true
+
+_.isNumber('3');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isobjectvalue"></a>`_.isObject(value)`
+<a href="#_isobjectvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10921 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isobject "See the npm package")
+
+Checks if `value` is the
+[language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types)
+of `Object`. *(e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)*
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is an object, else `false`.
+
+#### Example
+```js
+_.isObject({});
+// => true
+
+_.isObject([1, 2, 3]);
+// => true
+
+_.isObject(_.noop);
+// => true
+
+_.isObject(null);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isobjectlikevalue"></a>`_.isObjectLike(value)`
+<a href="#_isobjectlikevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L10950 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isobjectlike "See the npm package")
+
+Checks if `value` is object-like. A value is object-like if it's not `null`
+and has a `typeof` result of "object".
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is object-like, else `false`.
+
+#### Example
+```js
+_.isObjectLike({});
+// => true
+
+_.isObjectLike([1, 2, 3]);
+// => true
+
+_.isObjectLike(_.noop);
+// => false
+
+_.isObjectLike(null);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isplainobjectvalue"></a>`_.isPlainObject(value)`
+<a href="#_isplainobjectvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11208 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isplainobject "See the npm package")
+
+Checks if `value` is a plain object, that is, an object created by the
+`Object` constructor or one with a `[[Prototype]]` of `null`.
+
+#### Since
+0.8.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a plain object, else `false`.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+}
+
+_.isPlainObject(new Foo);
+// => false
+
+_.isPlainObject([1, 2, 3]);
+// => false
+
+_.isPlainObject({ 'x': 0, 'y': 0 });
+// => true
+
+_.isPlainObject(Object.create(null));
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isregexpvalue"></a>`_.isRegExp(value)`
+<a href="#_isregexpvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11240 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isregexp "See the npm package")
+
+Checks if `value` is classified as a `RegExp` object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isRegExp(/abc/);
+// => true
+
+_.isRegExp('/abc/');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_issafeintegervalue"></a>`_.isSafeInteger(value)`
+<a href="#_issafeintegervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11272 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.issafeinteger "See the npm package")
+
+Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+double precision number which isn't the result of a rounded unsafe integer.
+<br>
+<br>
+**Note:** This method is based on
+[`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is a safe integer, else `false`.
+
+#### Example
+```js
+_.isSafeInteger(3);
+// => true
+
+_.isSafeInteger(Number.MIN_VALUE);
+// => false
+
+_.isSafeInteger(Infinity);
+// => false
+
+_.isSafeInteger('3');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_issetvalue"></a>`_.isSet(value)`
+<a href="#_issetvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11294 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isset "See the npm package")
+
+Checks if `value` is classified as a `Set` object.
+
+#### Since
+4.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isSet(new Set);
+// => true
+
+_.isSet(new WeakSet);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isstringvalue"></a>`_.isString(value)`
+<a href="#_isstringvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11316 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isstring "See the npm package")
+
+Checks if `value` is classified as a `String` primitive or object.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isString('abc');
+// => true
+
+_.isString(1);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_issymbolvalue"></a>`_.isSymbol(value)`
+<a href="#_issymbolvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11339 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.issymbol "See the npm package")
+
+Checks if `value` is classified as a `Symbol` primitive or object.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isSymbol(Symbol.iterator);
+// => true
+
+_.isSymbol('abc');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_istypedarrayvalue"></a>`_.isTypedArray(value)`
+<a href="#_istypedarrayvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11362 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.istypedarray "See the npm package")
+
+Checks if `value` is classified as a typed array.
+
+#### Since
+3.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isTypedArray(new Uint8Array);
+// => true
+
+_.isTypedArray([]);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isundefinedvalue"></a>`_.isUndefined(value)`
+<a href="#_isundefinedvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11384 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isundefined "See the npm package")
+
+Checks if `value` is `undefined`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is `undefined`, else `false`.
+
+#### Example
+```js
+_.isUndefined(void 0);
+// => true
+
+_.isUndefined(null);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isweakmapvalue"></a>`_.isWeakMap(value)`
+<a href="#_isweakmapvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11406 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isweakmap "See the npm package")
+
+Checks if `value` is classified as a `WeakMap` object.
+
+#### Since
+4.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isWeakMap(new WeakMap);
+// => true
+
+_.isWeakMap(new Map);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_isweaksetvalue"></a>`_.isWeakSet(value)`
+<a href="#_isweaksetvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11428 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.isweakset "See the npm package")
+
+Checks if `value` is classified as a `WeakSet` object.
+
+#### Since
+4.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is correctly classified, else `false`.
+
+#### Example
+```js
+_.isWeakSet(new WeakSet);
+// => true
+
+_.isWeakSet(new Set);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ltvalue-other"></a>`_.lt(value, other)`
+<a href="#_ltvalue-other">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11455 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.lt "See the npm package")
+
+Checks if `value` is less than `other`.
+
+#### Since
+3.9.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is less than `other`, else `false`.
+
+#### Example
+```js
+_.lt(1, 3);
+// => true
+
+_.lt(3, 3);
+// => false
+
+_.lt(3, 1);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ltevalue-other"></a>`_.lte(value, other)`
+<a href="#_ltevalue-other">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11480 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.lte "See the npm package")
+
+Checks if `value` is less than or equal to `other`.
+
+#### Since
+3.9.0
+#### Arguments
+1. `value` *(&#42;)*: The value to compare.
+2. `other` *(&#42;)*: The other value to compare.
+
+#### Returns
+*(boolean)*: Returns `true` if `value` is less than or equal to `other`, else `false`.
+
+#### Example
+```js
+_.lte(1, 3);
+// => true
+
+_.lte(3, 3);
+// => true
+
+_.lte(3, 1);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_toarrayvalue"></a>`_.toArray(value)`
+<a href="#_toarrayvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11507 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.toarray "See the npm package")
+
+Converts `value` to an array.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to convert.
+
+#### Returns
+*(Array)*: Returns the converted array.
+
+#### Example
+```js
+_.toArray({ 'a': 1, 'b': 2 });
+// => [1, 2]
+
+_.toArray('abc');
+// => ['a', 'b', 'c']
+
+_.toArray(1);
+// => []
+
+_.toArray(null);
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tointegervalue"></a>`_.toInteger(value)`
+<a href="#_tointegervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11549 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tointeger "See the npm package")
+
+Converts `value` to an integer.
+<br>
+<br>
+**Note:** This function is loosely based on
+[`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to convert.
+
+#### Returns
+*(number)*: Returns the converted integer.
+
+#### Example
+```js
+_.toInteger(3);
+// => 3
+
+_.toInteger(Number.MIN_VALUE);
+// => 0
+
+_.toInteger(Infinity);
+// => 1.7976931348623157e+308
+
+_.toInteger('3');
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tolengthvalue"></a>`_.toLength(value)`
+<a href="#_tolengthvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11589 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tolength "See the npm package")
+
+Converts `value` to an integer suitable for use as the length of an
+array-like object.
+<br>
+<br>
+**Note:** This method is based on
+[`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to convert.
+
+#### Returns
+*(number)*: Returns the converted integer.
+
+#### Example
+```js
+_.toLength(3);
+// => 3
+
+_.toLength(Number.MIN_VALUE);
+// => 0
+
+_.toLength(Infinity);
+// => 4294967295
+
+_.toLength('3');
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tonumbervalue"></a>`_.toNumber(value)`
+<a href="#_tonumbervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11616 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tonumber "See the npm package")
+
+Converts `value` to a number.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to process.
+
+#### Returns
+*(number)*: Returns the number.
+
+#### Example
+```js
+_.toNumber(3);
+// => 3
+
+_.toNumber(Number.MIN_VALUE);
+// => 5e-324
+
+_.toNumber(Infinity);
+// => Infinity
+
+_.toNumber('3');
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_toplainobjectvalue"></a>`_.toPlainObject(value)`
+<a href="#_toplainobjectvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11661 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.toplainobject "See the npm package")
+
+Converts `value` to a plain object flattening inherited enumerable string
+keyed properties of `value` to own properties of the plain object.
+
+#### Since
+3.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to convert.
+
+#### Returns
+*(Object)*: Returns the converted plain object.
+
+#### Example
+```js
+function Foo() {
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.assign({ 'a': 1 }, new Foo);
+// => { 'a': 1, 'b': 2 }
+
+_.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+// => { 'a': 1, 'b': 2, 'c': 3 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tosafeintegervalue"></a>`_.toSafeInteger(value)`
+<a href="#_tosafeintegervalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11689 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tosafeinteger "See the npm package")
+
+Converts `value` to a safe integer. A safe integer can be compared and
+represented correctly.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to convert.
+
+#### Returns
+*(number)*: Returns the converted integer.
+
+#### Example
+```js
+_.toSafeInteger(3);
+// => 3
+
+_.toSafeInteger(Number.MIN_VALUE);
+// => 0
+
+_.toSafeInteger(Infinity);
+// => 9007199254740991
+
+_.toSafeInteger('3');
+// => 3
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tostringvalue"></a>`_.toString(value)`
+<a href="#_tostringvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11714 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tostring "See the npm package")
+
+Converts `value` to a string. An empty string is returned for `null`
+and `undefined` values. The sign of `-0` is preserved.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to process.
+
+#### Returns
+*(string)*: Returns the string.
+
+#### Example
+```js
+_.toString(null);
+// => ''
+
+_.toString(-0);
+// => '-0'
+
+_.toString([1, 2, 3]);
+// => '1,2,3'
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Math” Methods`
+
+<!-- div -->
+
+### <a id="_addaugend-addend"></a>`_.add(augend, addend)`
+<a href="#_addaugend-addend">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15229 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.add "See the npm package")
+
+Adds two numbers.
+
+#### Since
+3.4.0
+#### Arguments
+1. `augend` *(number)*: The first number in an addition.
+2. `addend` *(number)*: The second number in an addition.
+
+#### Returns
+*(number)*: Returns the total.
+
+#### Example
+```js
+_.add(6, 4);
+// => 10
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_ceilnumber-precision0"></a>`_.ceil(number, [precision=0])`
+<a href="#_ceilnumber-precision0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15254 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.ceil "See the npm package")
+
+Computes `number` rounded up to `precision`.
+
+#### Since
+3.10.0
+#### Arguments
+1. `number` *(number)*: The number to round up.
+2. `[precision=0]` *(number)*: The precision to round up to.
+
+#### Returns
+*(number)*: Returns the rounded up number.
+
+#### Example
+```js
+_.ceil(4.006);
+// => 5
+
+_.ceil(6.004, 2);
+// => 6.01
+
+_.ceil(6040, -2);
+// => 6100
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_dividedividend-divisor"></a>`_.divide(dividend, divisor)`
+<a href="#_dividedividend-divisor">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15271 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.divide "See the npm package")
+
+Divide two numbers.
+
+#### Since
+4.7.0
+#### Arguments
+1. `dividend` *(number)*: The first number in a division.
+2. `divisor` *(number)*: The second number in a division.
+
+#### Returns
+*(number)*: Returns the quotient.
+
+#### Example
+```js
+_.divide(6, 4);
+// => 1.5
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_floornumber-precision0"></a>`_.floor(number, [precision=0])`
+<a href="#_floornumber-precision0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15296 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.floor "See the npm package")
+
+Computes `number` rounded down to `precision`.
+
+#### Since
+3.10.0
+#### Arguments
+1. `number` *(number)*: The number to round down.
+2. `[precision=0]` *(number)*: The precision to round down to.
+
+#### Returns
+*(number)*: Returns the rounded down number.
+
+#### Example
+```js
+_.floor(4.006);
+// => 4
+
+_.floor(0.046, 2);
+// => 0.04
+
+_.floor(4060, -2);
+// => 4000
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_maxarray"></a>`_.max(array)`
+<a href="#_maxarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15316 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.max "See the npm package")
+
+Computes the maximum value of `array`. If `array` is empty or falsey,
+`undefined` is returned.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+
+#### Returns
+*(&#42;)*: Returns the maximum value.
+
+#### Example
+```js
+_.max([4, 2, 8, 6]);
+// => 8
+
+_.max([]);
+// => undefined
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_maxbyarray-iteratee_identity"></a>`_.maxBy(array, [iteratee=_.identity])`
+<a href="#_maxbyarray-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15346 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.maxby "See the npm package")
+
+This method is like `_.max` except that it accepts `iteratee` which is
+invoked for each element in `array` to generate the criterion by which
+the value is ranked. The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(&#42;)*: Returns the maximum value.
+
+#### Example
+```js
+var objects = [{ 'n': 1 }, { 'n': 2 }];
+
+_.maxBy(objects, function(o) { return o.n; });
+// => { 'n': 2 }
+
+// The `_.property` iteratee shorthand.
+_.maxBy(objects, 'n');
+// => { 'n': 2 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_meanarray"></a>`_.mean(array)`
+<a href="#_meanarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15366 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.mean "See the npm package")
+
+Computes the mean of the values in `array`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+
+#### Returns
+*(number)*: Returns the mean.
+
+#### Example
+```js
+_.mean([4, 2, 8, 6]);
+// => 5
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_meanbyarray-iteratee_identity"></a>`_.meanBy(array, [iteratee=_.identity])`
+<a href="#_meanbyarray-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15394 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.meanby "See the npm package")
+
+This method is like `_.mean` except that it accepts `iteratee` which is
+invoked for each element in `array` to generate the value to be averaged.
+The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.7.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(number)*: Returns the mean.
+
+#### Example
+```js
+var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+
+_.meanBy(objects, function(o) { return o.n; });
+// => 5
+
+// The `_.property` iteratee shorthand.
+_.meanBy(objects, 'n');
+// => 5
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_minarray"></a>`_.min(array)`
+<a href="#_minarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15416 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.min "See the npm package")
+
+Computes the minimum value of `array`. If `array` is empty or falsey,
+`undefined` is returned.
+
+#### Since
+0.1.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+
+#### Returns
+*(&#42;)*: Returns the minimum value.
+
+#### Example
+```js
+_.min([4, 2, 8, 6]);
+// => 2
+
+_.min([]);
+// => undefined
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_minbyarray-iteratee_identity"></a>`_.minBy(array, [iteratee=_.identity])`
+<a href="#_minbyarray-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15446 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.minby "See the npm package")
+
+This method is like `_.min` except that it accepts `iteratee` which is
+invoked for each element in `array` to generate the criterion by which
+the value is ranked. The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(&#42;)*: Returns the minimum value.
+
+#### Example
+```js
+var objects = [{ 'n': 1 }, { 'n': 2 }];
+
+_.minBy(objects, function(o) { return o.n; });
+// => { 'n': 1 }
+
+// The `_.property` iteratee shorthand.
+_.minBy(objects, 'n');
+// => { 'n': 1 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_multiplymultiplier-multiplicand"></a>`_.multiply(multiplier, multiplicand)`
+<a href="#_multiplymultiplier-multiplicand">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15467 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.multiply "See the npm package")
+
+Multiply two numbers.
+
+#### Since
+4.7.0
+#### Arguments
+1. `multiplier` *(number)*: The first number in a multiplication.
+2. `multiplicand` *(number)*: The second number in a multiplication.
+
+#### Returns
+*(number)*: Returns the product.
+
+#### Example
+```js
+_.multiply(6, 4);
+// => 24
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_roundnumber-precision0"></a>`_.round(number, [precision=0])`
+<a href="#_roundnumber-precision0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15492 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.round "See the npm package")
+
+Computes `number` rounded to `precision`.
+
+#### Since
+3.10.0
+#### Arguments
+1. `number` *(number)*: The number to round.
+2. `[precision=0]` *(number)*: The precision to round to.
+
+#### Returns
+*(number)*: Returns the rounded number.
+
+#### Example
+```js
+_.round(4.006);
+// => 4
+
+_.round(4.006, 2);
+// => 4.01
+
+_.round(4060, -2);
+// => 4100
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_subtractminuend-subtrahend"></a>`_.subtract(minuend, subtrahend)`
+<a href="#_subtractminuend-subtrahend">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15509 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.subtract "See the npm package")
+
+Subtract two numbers.
+
+#### Since
+4.0.0
+#### Arguments
+1. `minuend` *(number)*: The first number in a subtraction.
+2. `subtrahend` *(number)*: The second number in a subtraction.
+
+#### Returns
+*(number)*: Returns the difference.
+
+#### Example
+```js
+_.subtract(6, 4);
+// => 2
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sumarray"></a>`_.sum(array)`
+<a href="#_sumarray">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15527 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sum "See the npm package")
+
+Computes the sum of the values in `array`.
+
+#### Since
+3.4.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+
+#### Returns
+*(number)*: Returns the sum.
+
+#### Example
+```js
+_.sum([4, 2, 8, 6]);
+// => 20
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_sumbyarray-iteratee_identity"></a>`_.sumBy(array, [iteratee=_.identity])`
+<a href="#_sumbyarray-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15557 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.sumby "See the npm package")
+
+This method is like `_.sum` except that it accepts `iteratee` which is
+invoked for each element in `array` to generate the value to be summed.
+The iteratee is invoked with one argument: *(value)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(number)*: Returns the sum.
+
+#### Example
+```js
+var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+
+_.sumBy(objects, function(o) { return o.n; });
+// => 20
+
+// The `_.property` iteratee shorthand.
+_.sumBy(objects, 'n');
+// => 20
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Number” Methods`
+
+<!-- div -->
+
+### <a id="_clampnumber-lower-upper"></a>`_.clamp(number, [lower], upper)`
+<a href="#_clampnumber-lower-upper">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13139 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.clamp "See the npm package")
+
+Clamps `number` within the inclusive `lower` and `upper` bounds.
+
+#### Since
+4.0.0
+#### Arguments
+1. `number` *(number)*: The number to clamp.
+2. `[lower]` *(number)*: The lower bound.
+3. `upper` *(number)*: The upper bound.
+
+#### Returns
+*(number)*: Returns the clamped number.
+
+#### Example
+```js
+_.clamp(-10, -5, 5);
+// => -5
+
+_.clamp(10, -5, 5);
+// => 5
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_inrangenumber-start0-end"></a>`_.inRange(number, [start=0], end)`
+<a href="#_inrangenumber-start0-end">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13193 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.inrange "See the npm package")
+
+Checks if `n` is between `start` and up to, but not including, `end`. If
+`end` is not specified, it's set to `start` with `start` then set to `0`.
+If `start` is greater than `end` the params are swapped to support
+negative ranges.
+
+#### Since
+3.3.0
+#### Arguments
+1. `number` *(number)*: The number to check.
+2. `[start=0]` *(number)*: The start of the range.
+3. `end` *(number)*: The end of the range.
+
+#### Returns
+*(boolean)*: Returns `true` if `number` is in the range, else `false`.
+
+#### Example
+```js
+_.inRange(3, 2, 4);
+// => true
+
+_.inRange(4, 8);
+// => true
+
+_.inRange(4, 2);
+// => false
+
+_.inRange(2, 2);
+// => false
+
+_.inRange(1.2, 2);
+// => true
+
+_.inRange(5.2, 4);
+// => false
+
+_.inRange(-3, -2, -6);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_randomlower0-upper1-floating"></a>`_.random([lower=0], [upper=1], [floating])`
+<a href="#_randomlower0-upper1-floating">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13236 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.random "See the npm package")
+
+Produces a random number between the inclusive `lower` and `upper` bounds.
+If only one argument is provided a number between `0` and the given number
+is returned. If `floating` is `true`, or either `lower` or `upper` are
+floats, a floating-point number is returned instead of an integer.
+<br>
+<br>
+**Note:** JavaScript follows the IEEE-754 standard for resolving
+floating-point values which can produce unexpected results.
+
+#### Since
+0.7.0
+#### Arguments
+1. `[lower=0]` *(number)*: The lower bound.
+2. `[upper=1]` *(number)*: The upper bound.
+3. `[floating]` *(boolean)*: Specify returning a floating-point number.
+
+#### Returns
+*(number)*: Returns the random number.
+
+#### Example
+```js
+_.random(0, 5);
+// => an integer between 0 and 5
+
+_.random(5);
+// => also an integer between 0 and 5
+
+_.random(5, true);
+// => a floating-point number between 0 and 5
+
+_.random(1.2, 5.2);
+// => a floating-point number between 1.2 and 5.2
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Object” Methods`
+
+<!-- div -->
+
+### <a id="_assignobject-sources"></a>`_.assign(object, [sources])`
+<a href="#_assignobject-sources">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11752 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.assign "See the npm package")
+
+Assigns own enumerable string keyed properties of source objects to the
+destination object. Source objects are applied from left to right.
+Subsequent sources overwrite property assignments of previous sources.
+<br>
+<br>
+**Note:** This method mutates `object` and is loosely based on
+[`Object.assign`](https://mdn.io/Object/assign).
+
+#### Since
+0.10.0
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `[sources]` *(...Object)*: The source objects.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function Foo() {
+  this.c = 3;
+}
+
+function Bar() {
+  this.e = 5;
+}
+
+Foo.prototype.d = 4;
+Bar.prototype.f = 6;
+
+_.assign({ 'a': 1 }, new Foo, new Bar);
+// => { 'a': 1, 'c': 3, 'e': 5 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_assigninobject-sources"></a>`_.assignIn(object, [sources])`
+<a href="#_assigninobject-sources">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11795 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.assignin "See the npm package")
+
+This method is like `_.assign` except that it iterates over own and
+inherited source properties.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.0.0
+#### Aliases
+*_.extend*
+
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `[sources]` *(...Object)*: The source objects.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function Foo() {
+  this.b = 2;
+}
+
+function Bar() {
+  this.d = 4;
+}
+
+Foo.prototype.c = 3;
+Bar.prototype.e = 5;
+
+_.assignIn({ 'a': 1 }, new Foo, new Bar);
+// => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_assigninwithobject-sources-customizer"></a>`_.assignInWith(object, sources, [customizer])`
+<a href="#_assigninwithobject-sources-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11834 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.assigninwith "See the npm package")
+
+This method is like `_.assignIn` except that it accepts `customizer`
+which is invoked to produce the assigned values. If `customizer` returns
+`undefined`, assignment is handled by the method instead. The `customizer`
+is invoked with five arguments: *(objValue, srcValue, key, object, source)*.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.0.0
+#### Aliases
+*_.extendWith*
+
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `sources` *(...Object)*: The source objects.
+3. `[customizer]` *(Function)*: The function to customize assigned values.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function customizer(objValue, srcValue) {
+  return _.isUndefined(objValue) ? srcValue : objValue;
+}
+
+var defaults = _.partialRight(_.assignInWith, customizer);
+
+defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+// => { 'a': 1, 'b': 2 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_assignwithobject-sources-customizer"></a>`_.assignWith(object, sources, [customizer])`
+<a href="#_assignwithobject-sources-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11866 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.assignwith "See the npm package")
+
+This method is like `_.assign` except that it accepts `customizer`
+which is invoked to produce the assigned values. If `customizer` returns
+`undefined`, assignment is handled by the method instead. The `customizer`
+is invoked with five arguments: *(objValue, srcValue, key, object, source)*.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `sources` *(...Object)*: The source objects.
+3. `[customizer]` *(Function)*: The function to customize assigned values.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function customizer(objValue, srcValue) {
+  return _.isUndefined(objValue) ? srcValue : objValue;
+}
+
+var defaults = _.partialRight(_.assignWith, customizer);
+
+defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+// => { 'a': 1, 'b': 2 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_atobject-paths"></a>`_.at(object, [paths])`
+<a href="#_atobject-paths">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11890 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.at "See the npm package")
+
+Creates an array of values corresponding to `paths` of `object`.
+
+#### Since
+1.0.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[paths]` *(...(string|string&#91;&#93;))*: The property paths of elements to pick.
+
+#### Returns
+*(Array)*: Returns the new array of picked elements.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+
+_.at(object, ['a[0].b.c', 'a[1]']);
+// => [3, 4]
+
+_.at(['a', 'b', 'c'], 0, 2);
+// => ['a', 'c']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_createprototype-properties"></a>`_.create(prototype, [properties])`
+<a href="#_createprototype-properties">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11928 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.create "See the npm package")
+
+Creates an object that inherits from the `prototype` object. If a
+`properties` object is given, its own enumerable string keyed properties
+are assigned to the created object.
+
+#### Since
+2.3.0
+#### Arguments
+1. `prototype` *(Object)*: The object to inherit from.
+2. `[properties]` *(Object)*: The properties to assign to the object.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+function Shape() {
+  this.x = 0;
+  this.y = 0;
+}
+
+function Circle() {
+  Shape.call(this);
+}
+
+Circle.prototype = _.create(Shape.prototype, {
+  'constructor': Circle
+});
+
+var circle = new Circle;
+circle instanceof Circle;
+// => true
+
+circle instanceof Shape;
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_defaultsobject-sources"></a>`_.defaults(object, [sources])`
+<a href="#_defaultsobject-sources">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11954 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.defaults "See the npm package")
+
+Assigns own and inherited enumerable string keyed properties of source
+objects to the destination object for all destination properties that
+resolve to `undefined`. Source objects are applied from left to right.
+Once a property is set, additional values of the same property are ignored.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `[sources]` *(...Object)*: The source objects.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+_.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+// => { 'user': 'barney', 'age': 36 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_defaultsdeepobject-sources"></a>`_.defaultsDeep(object, [sources])`
+<a href="#_defaultsdeepobject-sources">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L11979 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.defaultsdeep "See the npm package")
+
+This method is like `_.defaults` except that it recursively assigns
+default properties.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+3.10.0
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `[sources]` *(...Object)*: The source objects.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+_.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
+// => { 'user': { 'name': 'barney', 'age': 36 } }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_findkeyobject-predicate_identity"></a>`_.findKey(object, [predicate=_.identity])`
+<a href="#_findkeyobject-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12020 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.findkey "See the npm package")
+
+This method is like `_.find` except that it returns the key of the first
+element `predicate` returns truthy for instead of the element itself.
+
+#### Since
+1.1.0
+#### Arguments
+1. `object` *(Object)*: The object to search.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(&#42;)*: Returns the key of the matched element, else `undefined`.
+
+#### Example
+```js
+var users = {
+  'barney':  { 'age': 36, 'active': true },
+  'fred':    { 'age': 40, 'active': false },
+  'pebbles': { 'age': 1,  'active': true }
+};
+
+_.findKey(users, function(o) { return o.age < 40; });
+// => 'barney' (iteration order is not guaranteed)
+
+// The `_.matches` iteratee shorthand.
+_.findKey(users, { 'age': 1, 'active': true });
+// => 'pebbles'
+
+// The `_.matchesProperty` iteratee shorthand.
+_.findKey(users, ['active', false]);
+// => 'fred'
+
+// The `_.property` iteratee shorthand.
+_.findKey(users, 'active');
+// => 'barney'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_findlastkeyobject-predicate_identity"></a>`_.findLastKey(object, [predicate=_.identity])`
+<a href="#_findlastkeyobject-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12060 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.findlastkey "See the npm package")
+
+This method is like `_.findKey` except that it iterates over elements of
+a collection in the opposite order.
+
+#### Since
+2.0.0
+#### Arguments
+1. `object` *(Object)*: The object to search.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(&#42;)*: Returns the key of the matched element, else `undefined`.
+
+#### Example
+```js
+var users = {
+  'barney':  { 'age': 36, 'active': true },
+  'fred':    { 'age': 40, 'active': false },
+  'pebbles': { 'age': 1,  'active': true }
+};
+
+_.findLastKey(users, function(o) { return o.age < 40; });
+// => returns 'pebbles' assuming `_.findKey` returns 'barney'
+
+// The `_.matches` iteratee shorthand.
+_.findLastKey(users, { 'age': 36, 'active': true });
+// => 'barney'
+
+// The `_.matchesProperty` iteratee shorthand.
+_.findLastKey(users, ['active', false]);
+// => 'fred'
+
+// The `_.property` iteratee shorthand.
+_.findLastKey(users, 'active');
+// => 'pebbles'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_forinobject-iteratee_identity"></a>`_.forIn(object, [iteratee=_.identity])`
+<a href="#_forinobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12092 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.forin "See the npm package")
+
+Iterates over own and inherited enumerable string keyed properties of an
+object and invokes `iteratee` for each property. The iteratee is invoked
+with three arguments: *(value, key, object)*. Iteratee functions may exit
+iteration early by explicitly returning `false`.
+
+#### Since
+0.3.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.forIn(new Foo, function(value, key) {
+  console.log(key);
+});
+// => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_forinrightobject-iteratee_identity"></a>`_.forInRight(object, [iteratee=_.identity])`
+<a href="#_forinrightobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12124 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.forinright "See the npm package")
+
+This method is like `_.forIn` except that it iterates over properties of
+`object` in the opposite order.
+
+#### Since
+2.0.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.forInRight(new Foo, function(value, key) {
+  console.log(key);
+});
+// => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_forownobject-iteratee_identity"></a>`_.forOwn(object, [iteratee=_.identity])`
+<a href="#_forownobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12158 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.forown "See the npm package")
+
+Iterates over own enumerable string keyed properties of an object and
+invokes `iteratee` for each property. The iteratee is invoked with three
+arguments: *(value, key, object)*. Iteratee functions may exit iteration
+early by explicitly returning `false`.
+
+#### Since
+0.3.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.forOwn(new Foo, function(value, key) {
+  console.log(key);
+});
+// => Logs 'a' then 'b' (iteration order is not guaranteed).
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_forownrightobject-iteratee_identity"></a>`_.forOwnRight(object, [iteratee=_.identity])`
+<a href="#_forownrightobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12188 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.forownright "See the npm package")
+
+This method is like `_.forOwn` except that it iterates over properties of
+`object` in the opposite order.
+
+#### Since
+2.0.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.forOwnRight(new Foo, function(value, key) {
+  console.log(key);
+});
+// => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_functionsobject"></a>`_.functions(object)`
+<a href="#_functionsobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12215 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.functions "See the npm package")
+
+Creates an array of function property names from own enumerable properties
+of `object`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The object to inspect.
+
+#### Returns
+*(Array)*: Returns the new array of property names.
+
+#### Example
+```js
+function Foo() {
+  this.a = _.constant('a');
+  this.b = _.constant('b');
+}
+
+Foo.prototype.c = _.constant('c');
+
+_.functions(new Foo);
+// => ['a', 'b']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_functionsinobject"></a>`_.functionsIn(object)`
+<a href="#_functionsinobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12242 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.functionsin "See the npm package")
+
+Creates an array of function property names from own and inherited
+enumerable properties of `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The object to inspect.
+
+#### Returns
+*(Array)*: Returns the new array of property names.
+
+#### Example
+```js
+function Foo() {
+  this.a = _.constant('a');
+  this.b = _.constant('b');
+}
+
+Foo.prototype.c = _.constant('c');
+
+_.functionsIn(new Foo);
+// => ['a', 'b', 'c']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_getobject-path-defaultvalue"></a>`_.get(object, path, [defaultValue])`
+<a href="#_getobject-path-defaultvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12271 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.get "See the npm package")
+
+Gets the value at `path` of `object`. If the resolved value is
+`undefined`, the `defaultValue` is used in its place.
+
+#### Since
+3.7.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+2. `path` *(Array|string)*: The path of the property to get.
+3. `[defaultValue]` *(&#42;)*: The value returned for `undefined` resolved values.
+
+#### Returns
+*(&#42;)*: Returns the resolved value.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': 3 } }] };
+
+_.get(object, 'a[0].b.c');
+// => 3
+
+_.get(object, ['a', '0', 'b', 'c']);
+// => 3
+
+_.get(object, 'a.b.c', 'default');
+// => 'default'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_hasobject-path"></a>`_.has(object, path)`
+<a href="#_hasobject-path">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12303 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.has "See the npm package")
+
+Checks if `path` is a direct property of `object`.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+2. `path` *(Array|string)*: The path to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `path` exists, else `false`.
+
+#### Example
+```js
+var object = { 'a': { 'b': 2 } };
+var other = _.create({ 'a': _.create({ 'b': 2 }) });
+
+_.has(object, 'a');
+// => true
+
+_.has(object, 'a.b');
+// => true
+
+_.has(object, ['a', 'b']);
+// => true
+
+_.has(other, 'a');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_hasinobject-path"></a>`_.hasIn(object, path)`
+<a href="#_hasinobject-path">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12333 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.hasin "See the npm package")
+
+Checks if `path` is a direct or inherited property of `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+2. `path` *(Array|string)*: The path to check.
+
+#### Returns
+*(boolean)*: Returns `true` if `path` exists, else `false`.
+
+#### Example
+```js
+var object = _.create({ 'a': _.create({ 'b': 2 }) });
+
+_.hasIn(object, 'a');
+// => true
+
+_.hasIn(object, 'a.b');
+// => true
+
+_.hasIn(object, ['a', 'b']);
+// => true
+
+_.hasIn(object, 'b');
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_invertobject"></a>`_.invert(object)`
+<a href="#_invertobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12355 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.invert "See the npm package")
+
+Creates an object composed of the inverted keys and values of `object`.
+If `object` contains duplicate values, subsequent values overwrite
+property assignments of previous values.
+
+#### Since
+0.7.0
+#### Arguments
+1. `object` *(Object)*: The object to invert.
+
+#### Returns
+*(Object)*: Returns the new inverted object.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': 2, 'c': 1 };
+
+_.invert(object);
+// => { '1': 'c', '2': 'b' }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_invertbyobject-iteratee_identity"></a>`_.invertBy(object, [iteratee=_.identity])`
+<a href="#_invertbyobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12386 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.invertby "See the npm package")
+
+This method is like `_.invert` except that the inverted object is generated
+from the results of running each element of `object` thru `iteratee`. The
+corresponding inverted value of each inverted key is an array of keys
+responsible for generating the inverted value. The iteratee is invoked
+with one argument: *(value)*.
+
+#### Since
+4.1.0
+#### Arguments
+1. `object` *(Object)*: The object to invert.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The iteratee invoked per element.
+
+#### Returns
+*(Object)*: Returns the new inverted object.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': 2, 'c': 1 };
+
+_.invertBy(object);
+// => { '1': ['a', 'c'], '2': ['b'] }
+
+_.invertBy(object, function(value) {
+  return 'group' + value;
+});
+// => { 'group1': ['a', 'c'], 'group2': ['b'] }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_invokeobject-path-args"></a>`_.invoke(object, path, [args])`
+<a href="#_invokeobject-path-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12412 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.invoke "See the npm package")
+
+Invokes the method at `path` of `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+2. `path` *(Array|string)*: The path of the method to invoke.
+3. `[args]` *(...&#42;)*: The arguments to invoke the method with.
+
+#### Returns
+*(&#42;)*: Returns the result of the invoked method.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+
+_.invoke(object, 'a[0].b.c.slice', 1, 3);
+// => [2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_keysobject"></a>`_.keys(object)`
+<a href="#_keysobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12442 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.keys "See the npm package")
+
+Creates an array of the own enumerable property names of `object`.
+<br>
+<br>
+**Note:** Non-object values are coerced to objects. See the
+[ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+for more details.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Array)*: Returns the array of property names.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.keys(new Foo);
+// => ['a', 'b'] (iteration order is not guaranteed)
+
+_.keys('hi');
+// => ['0', '1']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_keysinobject"></a>`_.keysIn(object)`
+<a href="#_keysinobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12485 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.keysin "See the npm package")
+
+Creates an array of the own and inherited enumerable property names of `object`.
+<br>
+<br>
+**Note:** Non-object values are coerced to objects.
+
+#### Since
+3.0.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Array)*: Returns the array of property names.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.keysIn(new Foo);
+// => ['a', 'b', 'c'] (iteration order is not guaranteed)
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_mapkeysobject-iteratee_identity"></a>`_.mapKeys(object, [iteratee=_.identity])`
+<a href="#_mapkeysobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12527 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.mapkeys "See the npm package")
+
+The opposite of `_.mapValues`; this method creates an object with the
+same values as `object` and keys generated by running each own enumerable
+string keyed property of `object` thru `iteratee`. The iteratee is invoked
+with three arguments: *(value, key, object)*.
+
+#### Since
+3.8.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Object)*: Returns the new mapped object.
+
+#### Example
+```js
+_.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+  return key + value;
+});
+// => { 'a1': 1, 'b2': 2 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_mapvaluesobject-iteratee_identity"></a>`_.mapValues(object, [iteratee=_.identity])`
+<a href="#_mapvaluesobject-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12566 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.mapvalues "See the npm package")
+
+Creates an object with the same keys as `object` and values generated
+by running each own enumerable string keyed property of `object` thru
+`iteratee`. The iteratee is invoked with three arguments:<br>
+*(value, key, object)*.
+
+#### Since
+2.4.0
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Array|Function|Object|string)*: The function invoked per iteration.
+
+#### Returns
+*(Object)*: Returns the new mapped object.
+
+#### Example
+```js
+var users = {
+  'fred':    { 'user': 'fred',    'age': 40 },
+  'pebbles': { 'user': 'pebbles', 'age': 1 }
+};
+
+_.mapValues(users, function(o) { return o.age; });
+// => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+
+// The `_.property` iteratee shorthand.
+_.mapValues(users, 'age');
+// => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_mergeobject-sources"></a>`_.merge(object, [sources])`
+<a href="#_mergeobject-sources">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12607 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.merge "See the npm package")
+
+This method is like `_.assign` except that it recursively merges own and
+inherited enumerable string keyed properties of source objects into the
+destination object. Source properties that resolve to `undefined` are
+skipped if a destination value exists. Array and plain object properties
+are merged recursively.Other objects and value types are overridden by
+assignment. Source objects are applied from left to right. Subsequent
+sources overwrite property assignments of previous sources.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+0.5.0
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `[sources]` *(...Object)*: The source objects.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+var users = {
+  'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
+};
+
+var ages = {
+  'data': [{ 'age': 36 }, { 'age': 40 }]
+};
+
+_.merge(users, ages);
+// => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_mergewithobject-sources-customizer"></a>`_.mergeWith(object, sources, customizer)`
+<a href="#_mergewithobject-sources-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12649 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.mergewith "See the npm package")
+
+This method is like `_.merge` except that it accepts `customizer` which
+is invoked to produce the merged values of the destination and source
+properties. If `customizer` returns `undefined`, merging is handled by the
+method instead. The `customizer` is invoked with seven arguments:<br>
+*(objValue, srcValue, key, object, source, stack)*.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The destination object.
+2. `sources` *(...Object)*: The source objects.
+3. `customizer` *(Function)*: The function to customize assigned values.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+function customizer(objValue, srcValue) {
+  if (_.isArray(objValue)) {
+    return objValue.concat(srcValue);
+  }
+}
+
+var object = {
+  'fruits': ['apple'],
+  'vegetables': ['beet']
+};
+
+var other = {
+  'fruits': ['banana'],
+  'vegetables': ['carrot']
+};
+
+_.mergeWith(object, other, customizer);
+// => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_omitobject-props"></a>`_.omit(object, [props])`
+<a href="#_omitobject-props">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12672 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.omit "See the npm package")
+
+The opposite of `_.pick`; this method creates an object composed of the
+own and inherited enumerable string keyed properties of `object` that are
+not omitted.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The source object.
+2. `[props]` *(...(string|string&#91;&#93;))*: The property identifiers to omit.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': '2', 'c': 3 };
+
+_.omit(object, ['a', 'c']);
+// => { 'b': '2' }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_omitbyobject-predicate_identity"></a>`_.omitBy(object, [predicate=_.identity])`
+<a href="#_omitbyobject-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12701 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.omitby "See the npm package")
+
+The opposite of `_.pickBy`; this method creates an object composed of
+the own and inherited enumerable string keyed properties of `object` that
+`predicate` doesn't return truthy for. The predicate is invoked with two
+arguments: *(value, key)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The source object.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per property.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': '2', 'c': 3 };
+
+_.omitBy(object, _.isNumber);
+// => { 'b': '2' }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pickobject-props"></a>`_.pick(object, [props])`
+<a href="#_pickobject-props">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12725 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pick "See the npm package")
+
+Creates an object composed of the picked `object` properties.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The source object.
+2. `[props]` *(...(string|string&#91;&#93;))*: The property identifiers to pick.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': '2', 'c': 3 };
+
+_.pick(object, ['a', 'c']);
+// => { 'a': 1, 'c': 3 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_pickbyobject-predicate_identity"></a>`_.pickBy(object, [predicate=_.identity])`
+<a href="#_pickbyobject-predicate_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12748 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pickby "See the npm package")
+
+Creates an object composed of the `object` properties `predicate` returns
+truthy for. The predicate is invoked with two arguments: *(value, key)*.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The source object.
+2. `[predicate=_.identity]` *(Array|Function|Object|string)*: The function invoked per property.
+
+#### Returns
+*(Object)*: Returns the new object.
+
+#### Example
+```js
+var object = { 'a': 1, 'b': '2', 'c': 3 };
+
+_.pickBy(object, _.isNumber);
+// => { 'a': 1, 'c': 3 }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_resultobject-path-defaultvalue"></a>`_.result(object, path, [defaultValue])`
+<a href="#_resultobject-path-defaultvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12781 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.result "See the npm package")
+
+This method is like `_.get` except that if the resolved value is a
+function it's invoked with the `this` binding of its parent object and
+its result is returned.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+2. `path` *(Array|string)*: The path of the property to resolve.
+3. `[defaultValue]` *(&#42;)*: The value returned for `undefined` resolved values.
+
+#### Returns
+*(&#42;)*: Returns the resolved value.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+
+_.result(object, 'a[0].b.c1');
+// => 3
+
+_.result(object, 'a[0].b.c2');
+// => 4
+
+_.result(object, 'a[0].b.c3', 'default');
+// => 'default'
+
+_.result(object, 'a[0].b.c3', _.constant('default'));
+// => 'default'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_setobject-path-value"></a>`_.set(object, path, value)`
+<a href="#_setobject-path-value">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12831 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.set "See the npm package")
+
+Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
+it's created. Arrays are created for missing index properties while objects
+are created for all other missing properties. Use `_.setWith` to customize
+`path` creation.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+3.7.0
+#### Arguments
+1. `object` *(Object)*: The object to modify.
+2. `path` *(Array|string)*: The path of the property to set.
+3. `value` *(&#42;)*: The value to set.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': 3 } }] };
+
+_.set(object, 'a[0].b.c', 4);
+console.log(object.a[0].b.c);
+// => 4
+
+_.set(object, ['x', '0', 'y', 'z'], 5);
+console.log(object.x[0].y.z);
+// => 5
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_setwithobject-path-value-customizer"></a>`_.setWith(object, path, value, [customizer])`
+<a href="#_setwithobject-path-value-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12859 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.setwith "See the npm package")
+
+This method is like `_.set` except that it accepts `customizer` which is
+invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+path creation is handled by the method instead. The `customizer` is invoked
+with three arguments: *(nsValue, key, nsObject)*.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The object to modify.
+2. `path` *(Array|string)*: The path of the property to set.
+3. `value` *(&#42;)*: The value to set.
+4. `[customizer]` *(Function)*: The function to customize assigned values.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+var object = {};
+
+_.setWith(object, '[0][1]', 'a', Object);
+// => { '0': { '1': 'a' } }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_topairsobject"></a>`_.toPairs(object)`
+<a href="#_topairsobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12887 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.topairs "See the npm package")
+
+Creates an array of own enumerable string keyed-value pairs for `object`
+which can be consumed by `_.fromPairs`.
+
+#### Since
+4.0.0
+#### Aliases
+*_.entries*
+
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Array)*: Returns the new array of key-value pairs.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.toPairs(new Foo);
+// => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_topairsinobject"></a>`_.toPairsIn(object)`
+<a href="#_topairsinobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12914 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.topairsin "See the npm package")
+
+Creates an array of own and inherited enumerable string keyed-value pairs
+for `object` which can be consumed by `_.fromPairs`.
+
+#### Since
+4.0.0
+#### Aliases
+*_.entriesIn*
+
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Array)*: Returns the new array of key-value pairs.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.toPairsIn(new Foo);
+// => [['a', 1], ['b', 2], ['c', 1]] (iteration order is not guaranteed)
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_transformobject-iteratee_identity-accumulator"></a>`_.transform(object, [iteratee=_.identity], [accumulator])`
+<a href="#_transformobject-iteratee_identity-accumulator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12947 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.transform "See the npm package")
+
+An alternative to `_.reduce`; this method transforms `object` to a new
+`accumulator` object which is the result of running each of its own
+enumerable string keyed properties thru `iteratee`, with each invocation
+potentially mutating the `accumulator` object. The iteratee is invoked
+with four arguments: *(accumulator, value, key, object)*. Iteratee functions
+may exit iteration early by explicitly returning `false`.
+
+#### Since
+1.3.0
+#### Arguments
+1. `object` *(Array|Object)*: The object to iterate over.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+3. `[accumulator]` *(&#42;)*: The custom accumulator value.
+
+#### Returns
+*(&#42;)*: Returns the accumulated value.
+
+#### Example
+```js
+_.transform([2, 3, 4], function(result, n) {
+  result.push(n *= n);
+  return n % 2 == 0;
+}, []);
+// => [4, 9]
+
+_.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+  (result[value] || (result[value] = [])).push(key);
+}, {});
+// => { '1': ['a', 'c'], '2': ['b'] }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unsetobject-path"></a>`_.unset(object, path)`
+<a href="#_unsetobject-path">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L12996 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unset "See the npm package")
+
+Removes the property at `path` of `object`.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `object` *(Object)*: The object to modify.
+2. `path` *(Array|string)*: The path of the property to unset.
+
+#### Returns
+*(boolean)*: Returns `true` if the property is deleted, else `false`.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': 7 } }] };
+_.unset(object, 'a[0].b.c');
+// => true
+
+console.log(object);
+// => { 'a': [{ 'b': {} }] };
+
+_.unset(object, ['a', '0', 'b', 'c']);
+// => true
+
+console.log(object);
+// => { 'a': [{ 'b': {} }] };
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_updateobject-path-updater"></a>`_.update(object, path, updater)`
+<a href="#_updateobject-path-updater">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13027 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.update "See the npm package")
+
+This method is like `_.set` except that accepts `updater` to produce the
+value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+is invoked with one argument: *(value)*.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.6.0
+#### Arguments
+1. `object` *(Object)*: The object to modify.
+2. `path` *(Array|string)*: The path of the property to set.
+3. `updater` *(Function)*: The function to produce the updated value.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': 3 } }] };
+
+_.update(object, 'a[0].b.c', function(n) { return n * n; });
+console.log(object.a[0].b.c);
+// => 9
+
+_.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+console.log(object.x[0].y.z);
+// => 0
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_updatewithobject-path-updater-customizer"></a>`_.updateWith(object, path, updater, [customizer])`
+<a href="#_updatewithobject-path-updater-customizer">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13055 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.updatewith "See the npm package")
+
+This method is like `_.update` except that it accepts `customizer` which is
+invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+path creation is handled by the method instead. The `customizer` is invoked
+with three arguments: *(nsValue, key, nsObject)*.
+<br>
+<br>
+**Note:** This method mutates `object`.
+
+#### Since
+4.6.0
+#### Arguments
+1. `object` *(Object)*: The object to modify.
+2. `path` *(Array|string)*: The path of the property to set.
+3. `updater` *(Function)*: The function to produce the updated value.
+4. `[customizer]` *(Function)*: The function to customize assigned values.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+var object = {};
+
+_.updateWith(object, '[0][1]', _.constant('a'), Object);
+// => { '0': { '1': 'a' } }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_valuesobject"></a>`_.values(object)`
+<a href="#_valuesobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13086 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.values "See the npm package")
+
+Creates an array of the own enumerable string keyed property values of `object`.
+<br>
+<br>
+**Note:** Non-object values are coerced to objects.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Array)*: Returns the array of property values.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.values(new Foo);
+// => [1, 2] (iteration order is not guaranteed)
+
+_.values('hi');
+// => ['h', 'i']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_valuesinobject"></a>`_.valuesIn(object)`
+<a href="#_valuesinobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13114 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.valuesin "See the npm package")
+
+Creates an array of the own and inherited enumerable string keyed property
+values of `object`.
+<br>
+<br>
+**Note:** Non-object values are coerced to objects.
+
+#### Since
+3.0.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Array)*: Returns the array of property values.
+
+#### Example
+```js
+function Foo() {
+  this.a = 1;
+  this.b = 2;
+}
+
+Foo.prototype.c = 3;
+
+_.valuesIn(new Foo);
+// => [1, 2, 3] (iteration order is not guaranteed)
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Seq” Methods`
+
+<!-- div -->
+
+### <a id="_value"></a>`_(value)`
+<a href="#_value">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1457 "View in source") [&#x24C9;][1]
+
+Creates a `lodash` object which wraps `value` to enable implicit method
+chain sequences. Methods that operate on and return arrays, collections,
+and functions can be chained together. Methods that retrieve a single value
+or may return a primitive value will automatically end the chain sequence
+and return the unwrapped value. Otherwise, the value must be unwrapped
+with `_#value`.
+<br>
+<br>
+Explicit chain sequences, which must be unwrapped with `_#value`, may be
+enabled using `_.chain`.
+<br>
+<br>
+The execution of chained methods is lazy, that is, it's deferred until
+`_#value` is implicitly or explicitly called.
+<br>
+<br>
+Lazy evaluation allows several methods to support shortcut fusion.
+Shortcut fusion is an optimization to merge iteratee calls; this avoids
+the creation of intermediate arrays and can greatly reduce the number of
+iteratee executions. Sections of a chain sequence qualify for shortcut
+fusion if the section is applied to an array of at least `200` elements
+and any iteratees accept only one argument. The heuristic for whether a
+section qualifies for shortcut fusion is subject to change.
+<br>
+<br>
+Chaining is supported in custom builds as long as the `_#value` method is
+directly or indirectly included in the build.
+<br>
+<br>
+In addition to lodash methods, wrappers have `Array` and `String` methods.
+<br>
+<br>
+The wrapper `Array` methods are:<br>
+`concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+<br>
+<br>
+The wrapper `String` methods are:<br>
+`replace` and `split`
+<br>
+<br>
+The wrapper methods that support shortcut fusion are:<br>
+`at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+`findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+`tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+<br>
+<br>
+The chainable wrapper methods are:<br>
+`after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+`before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+`commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+`curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+`difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+`dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+`flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+`flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+`functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+`intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+`keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+`memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+`nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+`overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+`pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+`pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+`remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+`slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+`takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+`toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+`union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+`unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+`valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+`zipObject`, `zipObjectDeep`, and `zipWith`
+<br>
+<br>
+The wrapper methods that are **not** chainable by default are:<br>
+`add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+`cloneDeep`, `cloneDeepWith`, `cloneWith`, `deburr`, `divide`, `each`,
+`eachRight`, `endsWith`, `eq`, `escape`, `escapeRegExp`, `every`, `find`,
+`findIndex`, `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `first`,
+`floor`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`,
+`forOwnRight`, `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`,
+`includes`, `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`,
+`isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, `isBuffer`,
+`isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, `isError`,
+`isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, `isMatch`,
+`isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`,
+`isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`,
+`isSet`, `isString`, `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`,
+`join`, `kebabCase`, `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`,
+`lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, `min`, `minBy`, `multiply`,
+`noConflict`, `noop`, `now`, `nth`, `pad`, `padEnd`, `padStart`, `parseInt`,
+`pop`, `random`, `reduce`, `reduceRight`, `repeat`, `result`, `round`,
+`runInContext`, `sample`, `shift`, `size`, `snakeCase`, `some`, `sortedIndex`,
+`sortedIndexBy`, `sortedLastIndex`, `sortedLastIndexBy`, `startCase`,
+`startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toInteger`,
+`toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, `toString`,
+`toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`,
+`uniqueId`, `upperCase`, `upperFirst`, `value`, and `words`
+
+#### Arguments
+1. `value` *(&#42;)*: The value to wrap in a `lodash` instance.
+
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+function square(n) {
+  return n * n;
+}
+
+var wrapped = _([1, 2, 3]);
+
+// Returns an unwrapped value.
+wrapped.reduce(_.add);
+// => 6
+
+// Returns a wrapped value.
+var squares = wrapped.map(square);
+
+_.isArray(squares);
+// => false
+
+_.isArray(squares.value());
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_chainvalue"></a>`_.chain(value)`
+<a href="#_chainvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7876 "View in source") [&#x24C9;][1]
+
+Creates a `lodash` wrapper instance that wraps `value` with explicit method
+chain sequences enabled. The result of such sequences must be unwrapped
+with `_#value`.
+
+#### Since
+1.3.0
+#### Arguments
+1. `value` *(&#42;)*: The value to wrap.
+
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney',  'age': 36 },
+  { 'user': 'fred',    'age': 40 },
+  { 'user': 'pebbles', 'age': 1 }
+];
+
+var youngest = _
+  .chain(users)
+  .sortBy('age')
+  .map(function(o) {
+    return o.user + ' is ' + o.age;
+  })
+  .head()
+  .value();
+// => 'pebbles is 1'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tapvalue-interceptor"></a>`_.tap(value, interceptor)`
+<a href="#_tapvalue-interceptor">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7905 "View in source") [&#x24C9;][1]
+
+This method invokes `interceptor` and returns `value`. The interceptor
+is invoked with one argument; *(value)*. The purpose of this method is to
+"tap into" a method chain sequence in order to modify intermediate results.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: The value to provide to `interceptor`.
+2. `interceptor` *(Function)*: The function to invoke.
+
+#### Returns
+*(&#42;)*: Returns `value`.
+
+#### Example
+```js
+_([1, 2, 3])
+ .tap(function(array) {
+   // Mutate input array.
+   array.pop();
+ })
+ .reverse()
+ .value();
+// => [2, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_thruvalue-interceptor"></a>`_.thru(value, interceptor)`
+<a href="#_thruvalue-interceptor">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7933 "View in source") [&#x24C9;][1]
+
+This method is like `_.tap` except that it returns the result of `interceptor`.
+The purpose of this method is to "pass thru" values replacing intermediate
+results in a method chain sequence.
+
+#### Since
+3.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to provide to `interceptor`.
+2. `interceptor` *(Function)*: The function to invoke.
+
+#### Returns
+*(&#42;)*: Returns the result of `interceptor`.
+
+#### Example
+```js
+_('  abc  ')
+ .chain()
+ .trim()
+ .thru(function(value) {
+   return [value];
+ })
+ .value();
+// => ['abc']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypesymboliterator"></a>`_.prototype[Symbol.iterator]()`
+<a href="#_prototypesymboliterator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8092 "View in source") [&#x24C9;][1]
+
+Enables the wrapper to be iterable.
+
+#### Since
+4.0.0
+#### Returns
+*(Object)*: Returns the wrapper object.
+
+#### Example
+```js
+var wrapped = _([1, 2]);
+
+wrapped[Symbol.iterator]() === wrapped;
+// => true
+
+Array.from(wrapped);
+// => [1, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypeatpaths"></a>`_.prototype.at([paths])`
+<a href="#_prototypeatpaths">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L7956 "View in source") [&#x24C9;][1]
+
+This method is the wrapper version of `_.at`.
+
+#### Since
+1.0.0
+#### Arguments
+1. `[paths]` *(...(string|string&#91;&#93;))*: The property paths of elements to pick.
+
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+
+_(object).at(['a[0].b.c', 'a[1]']).value();
+// => [3, 4]
+
+_(['a', 'b', 'c']).at(0, 2).value();
+// => ['a', 'c']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypechain"></a>`_.prototype.chain()`
+<a href="#_prototypechain">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8008 "View in source") [&#x24C9;][1]
+
+Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+
+#### Since
+0.1.0
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney', 'age': 36 },
+  { 'user': 'fred',   'age': 40 }
+];
+
+// A sequence without explicit chaining.
+_(users).head();
+// => { 'user': 'barney', 'age': 36 }
+
+// A sequence with explicit chaining.
+_(users)
+  .chain()
+  .head()
+  .pick('user')
+  .value();
+// => { 'user': 'barney' }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypecommit"></a>`_.prototype.commit()`
+<a href="#_prototypecommit">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8038 "View in source") [&#x24C9;][1]
+
+Executes the chain sequence and returns the wrapped result.
+
+#### Since
+3.2.0
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+var array = [1, 2];
+var wrapped = _(array).push(3);
+
+console.log(array);
+// => [1, 2]
+
+wrapped = wrapped.commit();
+console.log(array);
+// => [1, 2, 3]
+
+wrapped.last();
+// => 3
+
+console.log(array);
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypenext"></a>`_.prototype.next()`
+<a href="#_prototypenext">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8064 "View in source") [&#x24C9;][1]
+
+Gets the next value on a wrapped object following the
+[iterator protocol](https://mdn.io/iteration_protocols#iterator).
+
+#### Since
+4.0.0
+#### Returns
+*(Object)*: Returns the next iterator value.
+
+#### Example
+```js
+var wrapped = _([1, 2]);
+
+wrapped.next();
+// => { 'done': false, 'value': 1 }
+
+wrapped.next();
+// => { 'done': false, 'value': 2 }
+
+wrapped.next();
+// => { 'done': true, 'value': undefined }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypeplantvalue"></a>`_.prototype.plant(value)`
+<a href="#_prototypeplantvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8120 "View in source") [&#x24C9;][1]
+
+Creates a clone of the chain sequence planting `value` as the wrapped value.
+
+#### Since
+3.2.0
+#### Arguments
+1. `value` *(&#42;)*: The value to plant.
+
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+function square(n) {
+  return n * n;
+}
+
+var wrapped = _([1, 2]).map(square);
+var other = wrapped.plant([3, 4]);
+
+other.value();
+// => [9, 16]
+
+wrapped.value();
+// => [1, 4]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypereverse"></a>`_.prototype.reverse()`
+<a href="#_prototypereverse">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8160 "View in source") [&#x24C9;][1]
+
+This method is the wrapper version of `_.reverse`.
+<br>
+<br>
+**Note:** This method mutates the wrapped array.
+
+#### Since
+0.1.0
+#### Returns
+*(Object)*: Returns the new `lodash` wrapper instance.
+
+#### Example
+```js
+var array = [1, 2, 3];
+
+_(array).reverse().value()
+// => [3, 2, 1]
+
+console.log(array);
+// => [3, 2, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_prototypevalue"></a>`_.prototype.value()`
+<a href="#_prototypevalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L8192 "View in source") [&#x24C9;][1]
+
+Executes the chain sequence to resolve the unwrapped value.
+
+#### Since
+0.1.0
+#### Aliases
+*_.prototype.toJSON, _.prototype.valueOf*
+
+#### Returns
+*(&#42;)*: Returns the resolved unwrapped value.
+
+#### Example
+```js
+_([1, 2, 3]).value();
+// => [1, 2, 3]
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“String” Methods`
+
+<!-- div -->
+
+### <a id="_camelcasestring"></a>`_.camelCase([string=''])`
+<a href="#_camelcasestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13297 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.camelcase "See the npm package")
+
+Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the camel cased string.
+
+#### Example
+```js
+_.camelCase('Foo Bar');
+// => 'fooBar'
+
+_.camelCase('--foo-bar--');
+// => 'fooBar'
+
+_.camelCase('__FOO_BAR__');
+// => 'fooBar'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_capitalizestring"></a>`_.capitalize([string=''])`
+<a href="#_capitalizestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13317 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.capitalize "See the npm package")
+
+Converts the first character of `string` to upper case and the remaining
+to lower case.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to capitalize.
+
+#### Returns
+*(string)*: Returns the capitalized string.
+
+#### Example
+```js
+_.capitalize('FRED');
+// => 'Fred'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_deburrstring"></a>`_.deburr([string=''])`
+<a href="#_deburrstring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13338 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.deburr "See the npm package")
+
+Deburrs `string` by converting
+[latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+to basic latin letters and removing
+[combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to deburr.
+
+#### Returns
+*(string)*: Returns the deburred string.
+
+#### Example
+```js
+_.deburr('déjà vu');
+// => 'deja vu'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_endswithstring-target-positionstringlength"></a>`_.endsWith([string=''], [target], [position=string.length])`
+<a href="#_endswithstring-target-positionstringlength">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13366 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.endswith "See the npm package")
+
+Checks if `string` ends with the given target string.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to search.
+2. `[target]` *(string)*: The string to search for.
+3. `[position=string.length]` *(number)*: The position to search from.
+
+#### Returns
+*(boolean)*: Returns `true` if `string` ends with `target`, else `false`.
+
+#### Example
+```js
+_.endsWith('abc', 'c');
+// => true
+
+_.endsWith('abc', 'b');
+// => false
+
+_.endsWith('abc', 'b', 2);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_escapestring"></a>`_.escape([string=''])`
+<a href="#_escapestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13413 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.escape "See the npm package")
+
+Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to
+their corresponding HTML entities.
+<br>
+<br>
+**Note:** No other characters are escaped. To escape additional
+characters use a third-party library like [_he_](https://mths.be/he).
+<br>
+<br>
+Though the ">" character is escaped for symmetry, characters like
+">" and "/" don't need escaping in HTML and have no special meaning
+unless they're part of a tag or unquoted attribute value. See
+[Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+*(under "semi-related fun fact")* for more details.
+<br>
+<br>
+Backticks are escaped because in IE < `9`, they can break out of
+attribute values or HTML comments. See [#59](https://html5sec.org/#59),
+[#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
+[#133](https://html5sec.org/#133) of the
+[HTML5 Security Cheatsheet](https://html5sec.org/) for more details.
+<br>
+<br>
+When working with HTML you should always
+[quote attribute values](http://wonko.com/post/html-escaping) to reduce
+XSS vectors.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[string='']` *(string)*: The string to escape.
+
+#### Returns
+*(string)*: Returns the escaped string.
+
+#### Example
+```js
+_.escape('fred, barney, & pebbles');
+// => 'fred, barney, &amp; pebbles'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_escaperegexpstring"></a>`_.escapeRegExp([string=''])`
+<a href="#_escaperegexpstring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13435 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.escaperegexp "See the npm package")
+
+Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+"?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to escape.
+
+#### Returns
+*(string)*: Returns the escaped string.
+
+#### Example
+```js
+_.escapeRegExp('[lodash](https://lodash.com/)');
+// => '\[lodash\]\(https://lodash\.com/\)'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_kebabcasestring"></a>`_.kebabCase([string=''])`
+<a href="#_kebabcasestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13463 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.kebabcase "See the npm package")
+
+Converts `string` to
+[kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the kebab cased string.
+
+#### Example
+```js
+_.kebabCase('Foo Bar');
+// => 'foo-bar'
+
+_.kebabCase('fooBar');
+// => 'foo-bar'
+
+_.kebabCase('__FOO_BAR__');
+// => 'foo-bar'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_lowercasestring"></a>`_.lowerCase([string=''])`
+<a href="#_lowercasestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13487 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.lowercase "See the npm package")
+
+Converts `string`, as space separated words, to lower case.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the lower cased string.
+
+#### Example
+```js
+_.lowerCase('--Foo-Bar--');
+// => 'foo bar'
+
+_.lowerCase('fooBar');
+// => 'foo bar'
+
+_.lowerCase('__FOO_BAR__');
+// => 'foo bar'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_lowerfirststring"></a>`_.lowerFirst([string=''])`
+<a href="#_lowerfirststring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13508 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.lowerfirst "See the npm package")
+
+Converts the first character of `string` to lower case.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the converted string.
+
+#### Example
+```js
+_.lowerFirst('Fred');
+// => 'fred'
+
+_.lowerFirst('FRED');
+// => 'fRED'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_padstring-length0-chars"></a>`_.pad([string=''], [length=0], [chars=' '])`
+<a href="#_padstring-length0-chars">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13533 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.pad "See the npm package")
+
+Pads `string` on the left and right sides if it's shorter than `length`.
+Padding characters are truncated if they can't be evenly divided by `length`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to pad.
+2. `[length=0]` *(number)*: The padding length.
+3. `[chars=' ']` *(string)*: The string used as padding.
+
+#### Returns
+*(string)*: Returns the padded string.
+
+#### Example
+```js
+_.pad('abc', 8);
+// => '  abc   '
+
+_.pad('abc', 8, '_-');
+// => '_-abc_-_'
+
+_.pad('abc', 3);
+// => 'abc'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_padendstring-length0-chars"></a>`_.padEnd([string=''], [length=0], [chars=' '])`
+<a href="#_padendstring-length0-chars">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13572 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.padend "See the npm package")
+
+Pads `string` on the right side if it's shorter than `length`. Padding
+characters are truncated if they exceed `length`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to pad.
+2. `[length=0]` *(number)*: The padding length.
+3. `[chars=' ']` *(string)*: The string used as padding.
+
+#### Returns
+*(string)*: Returns the padded string.
+
+#### Example
+```js
+_.padEnd('abc', 6);
+// => 'abc   '
+
+_.padEnd('abc', 6, '_-');
+// => 'abc_-_'
+
+_.padEnd('abc', 3);
+// => 'abc'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_padstartstring-length0-chars"></a>`_.padStart([string=''], [length=0], [chars=' '])`
+<a href="#_padstartstring-length0-chars">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13605 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.padstart "See the npm package")
+
+Pads `string` on the left side if it's shorter than `length`. Padding
+characters are truncated if they exceed `length`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to pad.
+2. `[length=0]` *(number)*: The padding length.
+3. `[chars=' ']` *(string)*: The string used as padding.
+
+#### Returns
+*(string)*: Returns the padded string.
+
+#### Example
+```js
+_.padStart('abc', 6);
+// => '   abc'
+
+_.padStart('abc', 6, '_-');
+// => '_-_abc'
+
+_.padStart('abc', 3);
+// => 'abc'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_parseintstring-radix10"></a>`_.parseInt(string, [radix=10])`
+<a href="#_parseintstring-radix10">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13639 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.parseint "See the npm package")
+
+Converts `string` to an integer of the specified radix. If `radix` is
+`undefined` or `0`, a `radix` of `10` is used unless `value` is a
+hexadecimal, in which case a `radix` of `16` is used.
+<br>
+<br>
+**Note:** This method aligns with the
+[ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
+
+#### Since
+1.1.0
+#### Arguments
+1. `string` *(string)*: The string to convert.
+2. `[radix=10]` *(number)*: The radix to interpret `value` by.
+
+#### Returns
+*(number)*: Returns the converted integer.
+
+#### Example
+```js
+_.parseInt('08');
+// => 8
+
+_.map(['6', '08', '10'], _.parseInt);
+// => [6, 8, 10]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_repeatstring-n1"></a>`_.repeat([string=''], [n=1])`
+<a href="#_repeatstring-n1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13673 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.repeat "See the npm package")
+
+Repeats the given string `n` times.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to repeat.
+2. `[n=1]` *(number)*: The number of times to repeat the string.
+
+#### Returns
+*(string)*: Returns the repeated string.
+
+#### Example
+```js
+_.repeat('*', 3);
+// => '***'
+
+_.repeat('abc', 2);
+// => 'abcabc'
+
+_.repeat('abc', 0);
+// => ''
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_replacestring-pattern-replacement"></a>`_.replace([string=''], pattern, replacement)`
+<a href="#_replacestring-pattern-replacement">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13701 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.replace "See the npm package")
+
+Replaces matches for `pattern` in `string` with `replacement`.
+<br>
+<br>
+**Note:** This method is based on
+[`String#replace`](https://mdn.io/String/replace).
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to modify.
+2. `pattern` *(RegExp|string)*: The pattern to replace.
+3. `replacement` *(Function|string)*: The match replacement.
+
+#### Returns
+*(string)*: Returns the modified string.
+
+#### Example
+```js
+_.replace('Hi Fred', 'Fred', 'Barney');
+// => 'Hi Barney'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_snakecasestring"></a>`_.snakeCase([string=''])`
+<a href="#_snakecasestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13729 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.snakecase "See the npm package")
+
+Converts `string` to
+[snake case](https://en.wikipedia.org/wiki/Snake_case).
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the snake cased string.
+
+#### Example
+```js
+_.snakeCase('Foo Bar');
+// => 'foo_bar'
+
+_.snakeCase('fooBar');
+// => 'foo_bar'
+
+_.snakeCase('--FOO-BAR--');
+// => 'foo_bar'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_splitstring-separator-limit"></a>`_.split([string=''], separator, [limit])`
+<a href="#_splitstring-separator-limit">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13752 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.split "See the npm package")
+
+Splits `string` by `separator`.
+<br>
+<br>
+**Note:** This method is based on
+[`String#split`](https://mdn.io/String/split).
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to split.
+2. `separator` *(RegExp|string)*: The separator pattern to split by.
+3. `[limit]` *(number)*: The length to truncate results to.
+
+#### Returns
+*(Array)*: Returns the new array of string segments.
+
+#### Example
+```js
+_.split('a-b-c', '-', 2);
+// => ['a', 'b']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_startcasestring"></a>`_.startCase([string=''])`
+<a href="#_startcasestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13794 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.startcase "See the npm package")
+
+Converts `string` to
+[start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+
+#### Since
+3.1.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the start cased string.
+
+#### Example
+```js
+_.startCase('--foo-bar--');
+// => 'Foo Bar'
+
+_.startCase('fooBar');
+// => 'Foo Bar'
+
+_.startCase('__FOO_BAR__');
+// => 'FOO BAR'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_startswithstring-target-position0"></a>`_.startsWith([string=''], [target], [position=0])`
+<a href="#_startswithstring-target-position0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13821 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.startswith "See the npm package")
+
+Checks if `string` starts with the given target string.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to search.
+2. `[target]` *(string)*: The string to search for.
+3. `[position=0]` *(number)*: The position to search from.
+
+#### Returns
+*(boolean)*: Returns `true` if `string` starts with `target`, else `false`.
+
+#### Example
+```js
+_.startsWith('abc', 'a');
+// => true
+
+_.startsWith('abc', 'b');
+// => false
+
+_.startsWith('abc', 'b', 1);
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatestring-options-optionsescape_templatesettingsescape-optionsevaluate_templatesettingsevaluate-optionsimports_templatesettingsimports-optionsinterpolate_templatesettingsinterpolate-optionssourceurllodashtemplatesourcesn-optionsvariableobj"></a>`_.template([string=''], [options={}], [options.escape=_.templateSettings.escape], [options.evaluate=_.templateSettings.evaluate], [options.imports=_.templateSettings.imports], [options.interpolate=_.templateSettings.interpolate], [options.sourceURL='lodash.templateSources[n]'], [options.variable='obj'])`
+<a href="#_templatestring-options-optionsescape_templatesettingsescape-optionsevaluate_templatesettingsevaluate-optionsimports_templatesettingsimports-optionsinterpolate_templatesettingsinterpolate-optionssourceurllodashtemplatesourcesn-optionsvariableobj">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L13930 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.template "See the npm package")
+
+Creates a compiled template function that can interpolate data properties
+in "interpolate" delimiters, HTML-escape interpolated data properties in
+"escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+properties may be accessed as free variables in the template. If a setting
+object is given, it takes precedence over `_.templateSettings` values.
+<br>
+<br>
+**Note:** In the development build `_.template` utilizes
+[sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+for easier debugging.
+<br>
+<br>
+For more information on precompiling templates see
+[lodash's custom builds documentation](https://lodash.com/custom-builds).
+<br>
+<br>
+For more information on Chrome extension sandboxes see
+[Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+
+#### Since
+0.1.0
+#### Arguments
+1. `[string='']` *(string)*: The template string.
+2. `[options={}]` *(Object)*: The options object.
+3. `[options.escape=_.templateSettings.escape]` *(RegExp)*: The HTML "escape" delimiter.
+4. `[options.evaluate=_.templateSettings.evaluate]` *(RegExp)*: The "evaluate" delimiter.
+5. `[options.imports=_.templateSettings.imports]` *(Object)*: An object to import into the template as free variables.
+6. `[options.interpolate=_.templateSettings.interpolate]` *(RegExp)*: The "interpolate" delimiter.
+7. `[options.sourceURL='lodash.templateSources[n]']` *(string)*: The sourceURL of the compiled template.
+8. `[options.variable='obj']` *(string)*: The data object variable name.
+
+#### Returns
+*(Function)*: Returns the compiled template function.
+
+#### Example
+```js
+// Use the "interpolate" delimiter to create a compiled template.
+var compiled = _.template('hello <%= user %>!');
+compiled({ 'user': 'fred' });
+// => 'hello fred!'
+
+// Use the HTML "escape" delimiter to escape data property values.
+var compiled = _.template('<b><%- value %></b>');
+compiled({ 'value': '<script>' });
+// => '<b>&lt;script&gt;</b>'
+
+// Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+compiled({ 'users': ['fred', 'barney'] });
+// => '<li>fred</li><li>barney</li>'
+
+// Use the internal `print` function in "evaluate" delimiters.
+var compiled = _.template('<% print("hello " + user); %>!');
+compiled({ 'user': 'barney' });
+// => 'hello barney!'
+
+// Use the ES delimiter as an alternative to the default "interpolate" delimiter.
+var compiled = _.template('hello ${ user }!');
+compiled({ 'user': 'pebbles' });
+// => 'hello pebbles!'
+
+// Use custom template delimiters.
+_.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+var compiled = _.template('hello {{ user }}!');
+compiled({ 'user': 'mustache' });
+// => 'hello mustache!'
+
+// Use backslashes to treat delimiters as plain text.
+var compiled = _.template('<%= "\\<%- value %\\>" %>');
+compiled({ 'value': 'ignored' });
+// => '<%- value %>'
+
+// Use the `imports` option to import `jQuery` as `jq`.
+var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+compiled({ 'users': ['fred', 'barney'] });
+// => '<li>fred</li><li>barney</li>'
+
+// Use the `sourceURL` option to specify a custom sourceURL for the template.
+var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+compiled(data);
+// => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
+
+// Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+compiled.source;
+// => function(data) {
+//   var __t, __p = '';
+//   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+//   return __p;
+// }
+
+// Use the `source` property to inline compiled templates for meaningful
+// line numbers in error messages and stack traces.
+fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+  var JST = {\
+    "main": ' + _.template(mainText).source + '\
+  };\
+');
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_tolowerstring"></a>`_.toLower([string=''])`
+<a href="#_tolowerstring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14059 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.tolower "See the npm package")
+
+Converts `string`, as a whole, to lower case just like
+[String#toLowerCase](https://mdn.io/toLowerCase).
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the lower cased string.
+
+#### Example
+```js
+_.toLower('--Foo-Bar--');
+// => '--foo-bar--'
+
+_.toLower('fooBar');
+// => 'foobar'
+
+_.toLower('__FOO_BAR__');
+// => '__foo_bar__'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_toupperstring"></a>`_.toUpper([string=''])`
+<a href="#_toupperstring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14084 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.toupper "See the npm package")
+
+Converts `string`, as a whole, to upper case just like
+[String#toUpperCase](https://mdn.io/toUpperCase).
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the upper cased string.
+
+#### Example
+```js
+_.toUpper('--foo-bar--');
+// => '--FOO-BAR--'
+
+_.toUpper('fooBar');
+// => 'FOOBAR'
+
+_.toUpper('__foo_bar__');
+// => '__FOO_BAR__'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_trimstring-charswhitespace"></a>`_.trim([string=''], [chars=whitespace])`
+<a href="#_trimstring-charswhitespace">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14110 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.trim "See the npm package")
+
+Removes leading and trailing whitespace or specified characters from `string`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to trim.
+2. `[chars=whitespace]` *(string)*: The characters to trim.
+
+#### Returns
+*(string)*: Returns the trimmed string.
+
+#### Example
+```js
+_.trim('  abc  ');
+// => 'abc'
+
+_.trim('-_-abc-_-', '_-');
+// => 'abc'
+
+_.map(['  foo  ', '  bar  '], _.trim);
+// => ['foo', 'bar']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_trimendstring-charswhitespace"></a>`_.trimEnd([string=''], [chars=whitespace])`
+<a href="#_trimendstring-charswhitespace">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14145 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.trimend "See the npm package")
+
+Removes trailing whitespace or specified characters from `string`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to trim.
+2. `[chars=whitespace]` *(string)*: The characters to trim.
+
+#### Returns
+*(string)*: Returns the trimmed string.
+
+#### Example
+```js
+_.trimEnd('  abc  ');
+// => '  abc'
+
+_.trimEnd('-_-abc-_-', '_-');
+// => '-_-abc'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_trimstartstring-charswhitespace"></a>`_.trimStart([string=''], [chars=whitespace])`
+<a href="#_trimstartstring-charswhitespace">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14178 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.trimstart "See the npm package")
+
+Removes leading whitespace or specified characters from `string`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to trim.
+2. `[chars=whitespace]` *(string)*: The characters to trim.
+
+#### Returns
+*(string)*: Returns the trimmed string.
+
+#### Example
+```js
+_.trimStart('  abc  ');
+// => 'abc  '
+
+_.trimStart('-_-abc-_-', '_-');
+// => 'abc-_-'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_truncatestring-options-optionslength30-optionsomission-optionsseparator"></a>`_.truncate([string=''], [options={}], [options.length=30], [options.omission='...'], [options.separator])`
+<a href="#_truncatestring-options-optionslength30-optionsomission-optionsseparator">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14229 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.truncate "See the npm package")
+
+Truncates `string` if it's longer than the given maximum string length.
+The last characters of the truncated string are replaced with the omission
+string which defaults to "...".
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to truncate.
+2. `[options={}]` *(Object)*: The options object.
+3. `[options.length=30]` *(number)*: The maximum string length.
+4. `[options.omission='...']` *(string)*: The string to indicate text is omitted.
+5. `[options.separator]` *(RegExp|string)*: The separator pattern to truncate to.
+
+#### Returns
+*(string)*: Returns the truncated string.
+
+#### Example
+```js
+_.truncate('hi-diddly-ho there, neighborino');
+// => 'hi-diddly-ho there, neighbo...'
+
+_.truncate('hi-diddly-ho there, neighborino', {
+  'length': 24,
+  'separator': ' '
+});
+// => 'hi-diddly-ho there,...'
+
+_.truncate('hi-diddly-ho there, neighborino', {
+  'length': 24,
+  'separator': /,? +/
+});
+// => 'hi-diddly-ho there...'
+
+_.truncate('hi-diddly-ho there, neighborino', {
+  'omission': ' [...]'
+});
+// => 'hi-diddly-ho there, neig [...]'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_unescapestring"></a>`_.unescape([string=''])`
+<a href="#_unescapestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14304 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.unescape "See the npm package")
+
+The inverse of `_.escape`; this method converts the HTML entities
+`&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`, and `&#96;` in `string` to
+their corresponding characters.
+<br>
+<br>
+**Note:** No other HTML entities are unescaped. To unescape additional
+HTML entities use a third-party library like [_he_](https://mths.be/he).
+
+#### Since
+0.6.0
+#### Arguments
+1. `[string='']` *(string)*: The string to unescape.
+
+#### Returns
+*(string)*: Returns the unescaped string.
+
+#### Example
+```js
+_.unescape('fred, barney, &amp; pebbles');
+// => 'fred, barney, & pebbles'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_uppercasestring"></a>`_.upperCase([string=''])`
+<a href="#_uppercasestring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14331 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.uppercase "See the npm package")
+
+Converts `string`, as space separated words, to upper case.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the upper cased string.
+
+#### Example
+```js
+_.upperCase('--foo-bar');
+// => 'FOO BAR'
+
+_.upperCase('fooBar');
+// => 'FOO BAR'
+
+_.upperCase('__foo_bar__');
+// => 'FOO BAR'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_upperfirststring"></a>`_.upperFirst([string=''])`
+<a href="#_upperfirststring">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14352 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.upperfirst "See the npm package")
+
+Converts the first character of `string` to upper case.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to convert.
+
+#### Returns
+*(string)*: Returns the converted string.
+
+#### Example
+```js
+_.upperFirst('fred');
+// => 'Fred'
+
+_.upperFirst('FRED');
+// => 'FRED'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_wordsstring-pattern"></a>`_.words([string=''], [pattern])`
+<a href="#_wordsstring-pattern">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14373 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.words "See the npm package")
+
+Splits `string` into an array of its words.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[string='']` *(string)*: The string to inspect.
+2. `[pattern]` *(RegExp|string)*: The pattern to match words.
+
+#### Returns
+*(Array)*: Returns the words of `string`.
+
+#### Example
+```js
+_.words('fred, barney, & pebbles');
+// => ['fred', 'barney', 'pebbles']
+
+_.words('fred, barney, & pebbles', /[^, ]+/g);
+// => ['fred', 'barney', '&', 'pebbles']
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `“Util” Methods`
+
+<!-- div -->
+
+### <a id="_attemptfunc-args"></a>`_.attempt(func, [args])`
+<a href="#_attemptfunc-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14407 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.attempt "See the npm package")
+
+Attempts to invoke `func`, returning either the result or the caught error
+object. Any additional arguments are provided to `func` when it's invoked.
+
+#### Since
+3.0.0
+#### Arguments
+1. `func` *(Function)*: The function to attempt.
+2. `[args]` *(...&#42;)*: The arguments to invoke `func` with.
+
+#### Returns
+*(&#42;)*: Returns the `func` result or error object.
+
+#### Example
+```js
+// Avoid throwing errors for invalid selectors.
+var elements = _.attempt(function(selector) {
+  return document.querySelectorAll(selector);
+}, '>_>');
+
+if (_.isError(elements)) {
+  elements = [];
+}
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_bindallobject-methodnames"></a>`_.bindAll(object, methodNames)`
+<a href="#_bindallobject-methodnames">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14441 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.bindall "See the npm package")
+
+Binds methods of an object to the object itself, overwriting the existing
+method.
+<br>
+<br>
+**Note:** This method doesn't set the "length" property of bound functions.
+
+#### Since
+0.1.0
+#### Arguments
+1. `object` *(Object)*: The object to bind and assign the bound methods to.
+2. `methodNames` *(...(string|string&#91;&#93;))*: The object method names to bind.
+
+#### Returns
+*(Object)*: Returns `object`.
+
+#### Example
+```js
+var view = {
+  'label': 'docs',
+  'onClick': function() {
+    console.log('clicked ' + this.label);
+  }
+};
+
+_.bindAll(view, 'onClick');
+jQuery(element).on('click', view.onClick);
+// => Logs 'clicked docs' when clicked.
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_condpairs"></a>`_.cond(pairs)`
+<a href="#_condpairs">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14478 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.cond "See the npm package")
+
+Creates a function that iterates over `pairs` and invokes the corresponding
+function of the first predicate to return truthy. The predicate-function
+pairs are invoked with the `this` binding and arguments of the created
+function.
+
+#### Since
+4.0.0
+#### Arguments
+1. `pairs` *(Array)*: The predicate-function pairs.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var func = _.cond([
+  [_.matches({ 'a': 1 }),           _.constant('matches A')],
+  [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+  [_.constant(true),                _.constant('no match')]
+]);
+
+func({ 'a': 1, 'b': 2 });
+// => 'matches A'
+
+func({ 'a': 0, 'b': 1 });
+// => 'matches B'
+
+func({ 'a': '1', 'b': '2' });
+// => 'no match'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_conformssource"></a>`_.conforms(source)`
+<a href="#_conformssource">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14521 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.conforms "See the npm package")
+
+Creates a function that invokes the predicate properties of `source` with
+the corresponding property values of a given object, returning `true` if
+all predicates return truthy, else `false`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `source` *(Object)*: The object of property predicates to conform to.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney', 'age': 36 },
+  { 'user': 'fred',   'age': 40 }
+];
+
+_.filter(users, _.conforms({ 'age': _.partial(_.gt, _, 38) }));
+// => [{ 'user': 'fred', 'age': 40 }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_constantvalue"></a>`_.constant(value)`
+<a href="#_constantvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14542 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.constant "See the npm package")
+
+Creates a function that returns `value`.
+
+#### Since
+2.4.0
+#### Arguments
+1. `value` *(&#42;)*: The value to return from the new function.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var object = { 'user': 'fred' };
+var getter = _.constant(object);
+
+getter() === object;
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flowfuncs"></a>`_.flow([funcs])`
+<a href="#_flowfuncs">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14570 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flow "See the npm package")
+
+Creates a function that returns the result of invoking the given functions
+with the `this` binding of the created function, where each successive
+invocation is supplied the return value of the previous.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[funcs]` *(...(Function|Function&#91;&#93;))*: Functions to invoke.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+function square(n) {
+  return n * n;
+}
+
+var addSquare = _.flow(_.add, square);
+addSquare(1, 2);
+// => 9
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_flowrightfuncs"></a>`_.flowRight([funcs])`
+<a href="#_flowrightfuncs">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14593 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.flowright "See the npm package")
+
+This method is like `_.flow` except that it creates a function that
+invokes the given functions from right to left.
+
+#### Since
+3.0.0
+#### Arguments
+1. `[funcs]` *(...(Function|Function&#91;&#93;))*: Functions to invoke.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+function square(n) {
+  return n * n;
+}
+
+var addSquare = _.flowRight(square, _.add);
+addSquare(1, 2);
+// => 9
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_identityvalue"></a>`_.identity(value)`
+<a href="#_identityvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14611 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.identity "See the npm package")
+
+This method returns the first argument given to it.
+
+#### Since
+0.1.0
+#### Arguments
+1. `value` *(&#42;)*: Any value.
+
+#### Returns
+*(&#42;)*: Returns `value`.
+
+#### Example
+```js
+var object = { 'user': 'fred' };
+
+_.identity(object) === object;
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_iterateefunc_identity"></a>`_.iteratee([func=_.identity])`
+<a href="#_iterateefunc_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14657 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.iteratee "See the npm package")
+
+Creates a function that invokes `func` with the arguments of the created
+function. If `func` is a property name, the created function returns the
+property value for a given element. If `func` is an array or object, the
+created function returns `true` for elements that contain the equivalent
+source properties, otherwise it returns `false`.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[func=_.identity]` *(&#42;)*: The value to convert to a callback.
+
+#### Returns
+*(Function)*: Returns the callback.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney', 'age': 36, 'active': true },
+  { 'user': 'fred',   'age': 40, 'active': false }
+];
+
+// The `_.matches` iteratee shorthand.
+_.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+// => [{ 'user': 'barney', 'age': 36, 'active': true }]
+
+// The `_.matchesProperty` iteratee shorthand.
+_.filter(users, _.iteratee(['user', 'fred']));
+// => [{ 'user': 'fred', 'age': 40 }]
+
+// The `_.property` iteratee shorthand.
+_.map(users, _.iteratee('user'));
+// => ['barney', 'fred']
+
+// Create custom iteratee shorthands.
+_.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+  return !_.isRegExp(func) ? iteratee(func) : function(string) {
+    return func.test(string);
+  };
+});
+
+_.filter(['abc', 'def'], /ef/);
+// => ['def']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_matchessource"></a>`_.matches(source)`
+<a href="#_matchessource">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14685 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.matches "See the npm package")
+
+Creates a function that performs a partial deep comparison between a given
+object and `source`, returning `true` if the given object has equivalent
+property values, else `false`. The created function is equivalent to
+`_.isMatch` with a `source` partially applied.
+<br>
+<br>
+**Note:** This method supports comparing the same values as `_.isEqual`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `source` *(Object)*: The object of property values to match.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney', 'age': 36, 'active': true },
+  { 'user': 'fred',   'age': 40, 'active': false }
+];
+
+_.filter(users, _.matches({ 'age': 40, 'active': false }));
+// => [{ 'user': 'fred', 'age': 40, 'active': false }]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_matchespropertypath-srcvalue"></a>`_.matchesProperty(path, srcValue)`
+<a href="#_matchespropertypath-srcvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14713 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.matchesproperty "See the npm package")
+
+Creates a function that performs a partial deep comparison between the
+value at `path` of a given object to `srcValue`, returning `true` if the
+object value is equivalent, else `false`.
+<br>
+<br>
+**Note:** This method supports comparing the same values as `_.isEqual`.
+
+#### Since
+3.2.0
+#### Arguments
+1. `path` *(Array|string)*: The path of the property to get.
+2. `srcValue` *(&#42;)*: The value to match.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var users = [
+  { 'user': 'barney' },
+  { 'user': 'fred' }
+];
+
+_.find(users, _.matchesProperty('user', 'fred'));
+// => { 'user': 'fred' }
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_methodpath-args"></a>`_.method(path, [args])`
+<a href="#_methodpath-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14741 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.method "See the npm package")
+
+Creates a function that invokes the method at `path` of a given object.
+Any additional arguments are provided to the invoked method.
+
+#### Since
+3.7.0
+#### Arguments
+1. `path` *(Array|string)*: The path of the method to invoke.
+2. `[args]` *(...&#42;)*: The arguments to invoke the method with.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var objects = [
+  { 'a': { 'b': _.constant(2) } },
+  { 'a': { 'b': _.constant(1) } }
+];
+
+_.map(objects, _.method('a.b'));
+// => [2, 1]
+
+_.map(objects, _.method(['a', 'b']));
+// => [2, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_methodofobject-args"></a>`_.methodOf(object, [args])`
+<a href="#_methodofobject-args">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14770 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.methodof "See the npm package")
+
+The opposite of `_.method`; this method creates a function that invokes
+the method at a given path of `object`. Any additional arguments are
+provided to the invoked method.
+
+#### Since
+3.7.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+2. `[args]` *(...&#42;)*: The arguments to invoke the method with.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var array = _.times(3, _.constant),
+    object = { 'a': array, 'b': array, 'c': array };
+
+_.map(['a[2]', 'c[0]'], _.methodOf(object));
+// => [2, 0]
+
+_.map([['a', '2'], ['c', '0']], _.methodOf(object));
+// => [2, 0]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_mixinobjectlodash-source-options-optionschaintrue"></a>`_.mixin([object=lodash], source, [options={}], [options.chain=true])`
+<a href="#_mixinobjectlodash-source-options-optionschaintrue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14812 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.mixin "See the npm package")
+
+Adds all own enumerable string keyed function properties of a source
+object to the destination object. If `object` is a function, then methods
+are added to its prototype as well.
+<br>
+<br>
+**Note:** Use `_.runInContext` to create a pristine `lodash` function to
+avoid conflicts caused by modifying the original.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[object=lodash]` *(Function|Object)*: The destination object.
+2. `source` *(Object)*: The object of functions to add.
+3. `[options={}]` *(Object)*: The options object.
+4. `[options.chain=true]` *(boolean)*: Specify whether mixins are chainable.
+
+#### Returns
+*(&#42;)*: Returns `object`.
+
+#### Example
+```js
+function vowels(string) {
+  return _.filter(string, function(v) {
+    return /[aeiou]/i.test(v);
+  });
+}
+
+_.mixin({ 'vowels': vowels });
+_.vowels('fred');
+// => ['e']
+
+_('fred').vowels().value();
+// => ['e']
+
+_.mixin({ 'vowels': vowels }, { 'chain': false });
+_('fred').vowels();
+// => ['e']
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_noconflict"></a>`_.noConflict()`
+<a href="#_noconflict">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14861 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.noconflict "See the npm package")
+
+Reverts the `_` variable to its previous value and returns a reference to
+the `lodash` function.
+
+#### Since
+0.1.0
+#### Returns
+*(Function)*: Returns the `lodash` function.
+
+#### Example
+```js
+var lodash = _.noConflict();
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_noop"></a>`_.noop()`
+<a href="#_noop">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14883 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.noop "See the npm package")
+
+A no-operation function that returns `undefined` regardless of the
+arguments it receives.
+
+#### Since
+2.3.0
+#### Example
+```js
+var object = { 'user': 'fred' };
+
+_.noop(object) === undefined;
+// => true
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_nthargn0"></a>`_.nthArg([n=0])`
+<a href="#_nthargn0">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14907 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.ntharg "See the npm package")
+
+Creates a function that returns its nth argument. If `n` is negative,
+the nth argument from the end is returned.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[n=0]` *(number)*: The index of the argument to return.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var func = _.nthArg(1);
+func('a', 'b', 'c', 'd');
+// => 'b'
+
+var func = _.nthArg(-2);
+func('a', 'b', 'c', 'd');
+// => 'c'
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_overiteratees_identity"></a>`_.over([iteratees=[_.identity]])`
+<a href="#_overiteratees_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14932 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.over "See the npm package")
+
+Creates a function that invokes `iteratees` with the arguments it receives
+and returns their results.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[iteratees=[_.identity]]` *(...(Array|Array&#91;&#93;|Function|Function&#91;&#93;|Object|Object&#91;&#93;|string|string&#91;&#93;))*: The iteratees to invoke.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var func = _.over(Math.max, Math.min);
+
+func(1, 2, 3, 4);
+// => [4, 1]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_overeverypredicates_identity"></a>`_.overEvery([predicates=[_.identity]])`
+<a href="#_overeverypredicates_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14958 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.overevery "See the npm package")
+
+Creates a function that checks if **all** of the `predicates` return
+truthy when invoked with the arguments it receives.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[predicates=[_.identity]]` *(...(Array|Array&#91;&#93;|Function|Function&#91;&#93;|Object|Object&#91;&#93;|string|string&#91;&#93;))*: The predicates to check.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var func = _.overEvery(Boolean, isFinite);
+
+func('1');
+// => true
+
+func(null);
+// => false
+
+func(NaN);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_oversomepredicates_identity"></a>`_.overSome([predicates=[_.identity]])`
+<a href="#_oversomepredicates_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L14984 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.oversome "See the npm package")
+
+Creates a function that checks if **any** of the `predicates` return
+truthy when invoked with the arguments it receives.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[predicates=[_.identity]]` *(...(Array|Array&#91;&#93;|Function|Function&#91;&#93;|Object|Object&#91;&#93;|string|string&#91;&#93;))*: The predicates to check.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var func = _.overSome(Boolean, isFinite);
+
+func('1');
+// => true
+
+func(null);
+// => true
+
+func(NaN);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_propertypath"></a>`_.property(path)`
+<a href="#_propertypath">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15008 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.property "See the npm package")
+
+Creates a function that returns the value at `path` of a given object.
+
+#### Since
+2.4.0
+#### Arguments
+1. `path` *(Array|string)*: The path of the property to get.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var objects = [
+  { 'a': { 'b': 2 } },
+  { 'a': { 'b': 1 } }
+];
+
+_.map(objects, _.property('a.b'));
+// => [2, 1]
+
+_.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
+// => [1, 2]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_propertyofobject"></a>`_.propertyOf(object)`
+<a href="#_propertyofobject">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15033 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.propertyof "See the npm package")
+
+The opposite of `_.property`; this method creates a function that returns
+the value at a given path of `object`.
+
+#### Since
+3.0.0
+#### Arguments
+1. `object` *(Object)*: The object to query.
+
+#### Returns
+*(Function)*: Returns the new function.
+
+#### Example
+```js
+var array = [0, 1, 2],
+    object = { 'a': array, 'b': array, 'c': array };
+
+_.map(['a[2]', 'c[0]'], _.propertyOf(object));
+// => [2, 0]
+
+_.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+// => [2, 0]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_rangestart0-end-step1"></a>`_.range([start=0], end, [step=1])`
+<a href="#_rangestart0-end-step1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15080 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.range "See the npm package")
+
+Creates an array of numbers *(positive and/or negative)* progressing from
+`start` up to, but not including, `end`. A step of `-1` is used if a negative
+`start` is specified without an `end` or `step`. If `end` is not specified,
+it's set to `start` with `start` then set to `0`.
+<br>
+<br>
+**Note:** JavaScript follows the IEEE-754 standard for resolving
+floating-point values which can produce unexpected results.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[start=0]` *(number)*: The start of the range.
+2. `end` *(number)*: The end of the range.
+3. `[step=1]` *(number)*: The value to increment or decrement by.
+
+#### Returns
+*(Array)*: Returns the new array of numbers.
+
+#### Example
+```js
+_.range(4);
+// => [0, 1, 2, 3]
+
+_.range(-4);
+// => [0, -1, -2, -3]
+
+_.range(1, 5);
+// => [1, 2, 3, 4]
+
+_.range(0, 20, 5);
+// => [0, 5, 10, 15]
+
+_.range(0, -4, -1);
+// => [0, -1, -2, -3]
+
+_.range(1, 4, 0);
+// => [1, 1, 1]
+
+_.range(0);
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_rangerightstart0-end-step1"></a>`_.rangeRight([start=0], end, [step=1])`
+<a href="#_rangerightstart0-end-step1">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15118 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.rangeright "See the npm package")
+
+This method is like `_.range` except that it populates values in
+descending order.
+
+#### Since
+4.0.0
+#### Arguments
+1. `[start=0]` *(number)*: The start of the range.
+2. `end` *(number)*: The end of the range.
+3. `[step=1]` *(number)*: The value to increment or decrement by.
+
+#### Returns
+*(Array)*: Returns the new array of numbers.
+
+#### Example
+```js
+_.rangeRight(4);
+// => [3, 2, 1, 0]
+
+_.rangeRight(-4);
+// => [-3, -2, -1, 0]
+
+_.rangeRight(1, 5);
+// => [4, 3, 2, 1]
+
+_.rangeRight(0, 20, 5);
+// => [15, 10, 5, 0]
+
+_.rangeRight(0, -4, -1);
+// => [-3, -2, -1, 0]
+
+_.rangeRight(1, 4, 0);
+// => [1, 1, 1]
+
+_.rangeRight(0);
+// => []
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_runincontextcontextroot"></a>`_.runInContext([context=root])`
+<a href="#_runincontextcontextroot">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1239 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.runincontext "See the npm package")
+
+Create a new pristine `lodash` function using the `context` object.
+
+#### Since
+1.1.0
+#### Arguments
+1. `[context=root]` *(Object)*: The context object.
+
+#### Returns
+*(Function)*: Returns a new `lodash` function.
+
+#### Example
+```js
+_.mixin({ 'foo': _.constant('foo') });
+
+var lodash = _.runInContext();
+lodash.mixin({ 'bar': lodash.constant('bar') });
+
+_.isFunction(_.foo);
+// => true
+_.isFunction(_.bar);
+// => false
+
+lodash.isFunction(lodash.foo);
+// => false
+lodash.isFunction(lodash.bar);
+// => true
+
+// Use `context` to mock `Date#getTime` use in `_.now`.
+var mock = _.runInContext({
+  'Date': function() {
+    return { 'getTime': getTimeMock };
+  }
+});
+
+// Create a suped-up `defer` in Node.js.
+var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_timesn-iteratee_identity"></a>`_.times(n, [iteratee=_.identity])`
+<a href="#_timesn-iteratee_identity">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15139 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.times "See the npm package")
+
+Invokes the iteratee `n` times, returning an array of the results of
+each invocation. The iteratee is invoked with one argument; *(index)*.
+
+#### Since
+0.1.0
+#### Arguments
+1. `n` *(number)*: The number of times to invoke `iteratee`.
+2. `[iteratee=_.identity]` *(Function)*: The function invoked per iteration.
+
+#### Returns
+*(Array)*: Returns the array of results.
+
+#### Example
+```js
+_.times(3, String);
+// => ['0', '1', '2']
+
+ _.times(4, _.constant(true));
+// => [true, true, true, true]
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_topathvalue"></a>`_.toPath(value)`
+<a href="#_topathvalue">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15183 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.topath "See the npm package")
+
+Converts `value` to a property path array.
+
+#### Since
+4.0.0
+#### Arguments
+1. `value` *(&#42;)*: The value to convert.
+
+#### Returns
+*(Array)*: Returns the new property path array.
+
+#### Example
+```js
+_.toPath('a.b.c');
+// => ['a', 'b', 'c']
+
+_.toPath('a[0].b.c');
+// => ['a', '0', 'b', 'c']
+
+var path = ['a', 'b', 'c'],
+    newPath = _.toPath(path);
+
+console.log(newPath);
+// => ['a', 'b', 'c']
+
+console.log(path === newPath);
+// => false
+```
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_uniqueidprefix"></a>`_.uniqueId([prefix=''])`
+<a href="#_uniqueidprefix">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15207 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.uniqueid "See the npm package")
+
+Generates a unique ID. If `prefix` is given, the ID is appended to it.
+
+#### Since
+0.1.0
+#### Arguments
+1. `[prefix='']` *(string)*: The value to prefix the ID with.
+
+#### Returns
+*(string)*: Returns the unique ID.
+
+#### Example
+```js
+_.uniqueId('contact_');
+// => 'contact_104'
+
+_.uniqueId();
+// => '105'
+```
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Properties`
+
+<!-- div -->
+
+### <a id="_version"></a>`_.VERSION`
+<a href="#_version">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L15894 "View in source") [&#x24C9;][1]
+
+(string): The semantic version number.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatesettings"></a>`_.templateSettings`
+<a href="#_templatesettings">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1502 "View in source") [&#x24C9;][1] [&#x24C3;](https://www.npmjs.com/package/lodash.templatesettings "See the npm package")
+
+(Object): By default, the template delimiters used by lodash are like those in
+embedded Ruby *(ERB)*. Change the following template settings to use
+alternative delimiters.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatesettingsescape"></a>`_.templateSettings.escape`
+<a href="#_templatesettingsescape">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1510 "View in source") [&#x24C9;][1]
+
+(RegExp): Used to detect `data` property values to be HTML-escaped.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatesettingsevaluate"></a>`_.templateSettings.evaluate`
+<a href="#_templatesettingsevaluate">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1518 "View in source") [&#x24C9;][1]
+
+(RegExp): Used to detect code to be evaluated.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatesettingsimports"></a>`_.templateSettings.imports`
+<a href="#_templatesettingsimports">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1542 "View in source") [&#x24C9;][1]
+
+(Object): Used to import variables into the compiled template.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatesettingsinterpolate"></a>`_.templateSettings.interpolate`
+<a href="#_templatesettingsinterpolate">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1526 "View in source") [&#x24C9;][1]
+
+(RegExp): Used to detect `data` property values to inject.
+
+* * *
+
+<!-- /div -->
+
+<!-- div -->
+
+### <a id="_templatesettingsvariable"></a>`_.templateSettings.variable`
+<a href="#_templatesettingsvariable">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1534 "View in source") [&#x24C9;][1]
+
+(string): Used to reference the data object in the template text.
+
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- div -->
+
+## `Methods`
+
+<!-- div -->
+
+### <a id="_templatesettingsimports_"></a>`_.templateSettings.imports._`
+<a href="#_templatesettingsimports_">#</a> [&#x24C8;](https://github.com/lodash/lodash/blob/4.11.2/lodash.js#L1550 "View in source") [&#x24C9;][1]
+
+A reference to the `lodash` function.
+
+* * *
+
+<!-- /div -->
+
+<!-- /div -->
+
+<!-- /div -->
+
+ [1]: #array "Jump back to the TOC."
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_baseConvert.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_baseConvert.js
new file mode 100644
index 0000000..e7e631c
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_baseConvert.js
@@ -0,0 +1,469 @@
+var mapping = require('./_mapping'),
+    mutateMap = mapping.mutate,
+    fallbackHolder = require('./placeholder');
+
+/**
+ * Creates a function, with an arity of `n`, that invokes `func` with the
+ * arguments it receives.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {number} n The arity of the new function.
+ * @returns {Function} Returns the new function.
+ */
+function baseArity(func, n) {
+  return n == 2
+    ? function(a, b) { return func.apply(undefined, arguments); }
+    : function(a) { return func.apply(undefined, arguments); };
+}
+
+/**
+ * Creates a function that invokes `func`, with up to `n` arguments, ignoring
+ * any additional arguments.
+ *
+ * @private
+ * @param {Function} func The function to cap arguments for.
+ * @param {number} n The arity cap.
+ * @returns {Function} Returns the new function.
+ */
+function baseAry(func, n) {
+  return n == 2
+    ? function(a, b) { return func(a, b); }
+    : function(a) { return func(a); };
+}
+
+/**
+ * Creates a clone of `array`.
+ *
+ * @private
+ * @param {Array} array The array to clone.
+ * @returns {Array} Returns the cloned array.
+ */
+function cloneArray(array) {
+  var length = array ? array.length : 0,
+      result = Array(length);
+
+  while (length--) {
+    result[length] = array[length];
+  }
+  return result;
+}
+
+/**
+ * Creates a function that clones a given object using the assignment `func`.
+ *
+ * @private
+ * @param {Function} func The assignment function.
+ * @returns {Function} Returns the new cloner function.
+ */
+function createCloner(func) {
+  return function(object) {
+    return func({}, object);
+  };
+}
+
+/**
+ * Creates a function that wraps `func` and uses `cloner` to clone the first
+ * argument it receives.
+ *
+ * @private
+ * @param {Function} func The function to wrap.
+ * @param {Function} cloner The function to clone arguments.
+ * @returns {Function} Returns the new immutable function.
+ */
+function immutWrap(func, cloner) {
+  return function() {
+    var length = arguments.length;
+    if (!length) {
+      return result;
+    }
+    var args = Array(length);
+    while (length--) {
+      args[length] = arguments[length];
+    }
+    var result = args[0] = cloner.apply(undefined, args);
+    func.apply(undefined, args);
+    return result;
+  };
+}
+
+/**
+ * The base implementation of `convert` which accepts a `util` object of methods
+ * required to perform conversions.
+ *
+ * @param {Object} util The util object.
+ * @param {string} name The name of the function to convert.
+ * @param {Function} func The function to convert.
+ * @param {Object} [options] The options object.
+ * @param {boolean} [options.cap=true] Specify capping iteratee arguments.
+ * @param {boolean} [options.curry=true] Specify currying.
+ * @param {boolean} [options.fixed=true] Specify fixed arity.
+ * @param {boolean} [options.immutable=true] Specify immutable operations.
+ * @param {boolean} [options.rearg=true] Specify rearranging arguments.
+ * @returns {Function|Object} Returns the converted function or object.
+ */
+function baseConvert(util, name, func, options) {
+  var setPlaceholder,
+      isLib = typeof name == 'function',
+      isObj = name === Object(name);
+
+  if (isObj) {
+    options = func;
+    func = name;
+    name = undefined;
+  }
+  if (func == null) {
+    throw new TypeError;
+  }
+  options || (options = {});
+
+  var config = {
+    'cap': 'cap' in options ? options.cap : true,
+    'curry': 'curry' in options ? options.curry : true,
+    'fixed': 'fixed' in options ? options.fixed : true,
+    'immutable': 'immutable' in options ? options.immutable : true,
+    'rearg': 'rearg' in options ? options.rearg : true
+  };
+
+  var forceCurry = ('curry' in options) && options.curry,
+      forceFixed = ('fixed' in options) && options.fixed,
+      forceRearg = ('rearg' in options) && options.rearg,
+      placeholder = isLib ? func : fallbackHolder,
+      pristine = isLib ? func.runInContext() : undefined;
+
+  var helpers = isLib ? func : {
+    'ary': util.ary,
+    'assign': util.assign,
+    'clone': util.clone,
+    'curry': util.curry,
+    'forEach': util.forEach,
+    'isArray': util.isArray,
+    'isFunction': util.isFunction,
+    'iteratee': util.iteratee,
+    'keys': util.keys,
+    'rearg': util.rearg,
+    'spread': util.spread,
+    'toPath': util.toPath
+  };
+
+  var ary = helpers.ary,
+      assign = helpers.assign,
+      clone = helpers.clone,
+      curry = helpers.curry,
+      each = helpers.forEach,
+      isArray = helpers.isArray,
+      isFunction = helpers.isFunction,
+      keys = helpers.keys,
+      rearg = helpers.rearg,
+      spread = helpers.spread,
+      toPath = helpers.toPath;
+
+  var aryMethodKeys = keys(mapping.aryMethod);
+
+  var wrappers = {
+    'castArray': function(castArray) {
+      return function() {
+        var value = arguments[0];
+        return isArray(value)
+          ? castArray(cloneArray(value))
+          : castArray.apply(undefined, arguments);
+      };
+    },
+    'iteratee': function(iteratee) {
+      return function() {
+        var func = arguments[0],
+            arity = arguments[1],
+            result = iteratee(func, arity),
+            length = result.length;
+
+        if (config.cap && typeof arity == 'number') {
+          arity = arity > 2 ? (arity - 2) : 1;
+          return (length && length <= arity) ? result : baseAry(result, arity);
+        }
+        return result;
+      };
+    },
+    'mixin': function(mixin) {
+      return function(source) {
+        var func = this;
+        if (!isFunction(func)) {
+          return mixin(func, Object(source));
+        }
+        var methods = [],
+            methodNames = [];
+
+        each(keys(source), function(key) {
+          var value = source[key];
+          if (isFunction(value)) {
+            methodNames.push(key);
+            methods.push(func.prototype[key]);
+          }
+        });
+
+        mixin(func, Object(source));
+
+        each(methodNames, function(methodName, index) {
+          var method = methods[index];
+          if (isFunction(method)) {
+            func.prototype[methodName] = method;
+          } else {
+            delete func.prototype[methodName];
+          }
+        });
+        return func;
+      };
+    },
+    'runInContext': function(runInContext) {
+      return function(context) {
+        return baseConvert(util, runInContext(context), options);
+      };
+    }
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a clone of `object` by `path`.
+   *
+   * @private
+   * @param {Object} object The object to clone.
+   * @param {Array|string} path The path to clone by.
+   * @returns {Object} Returns the cloned object.
+   */
+  function cloneByPath(object, path) {
+    path = toPath(path);
+
+    var index = -1,
+        length = path.length,
+        result = clone(Object(object)),
+        nested = result;
+
+    while (nested != null && ++index < length) {
+      var key = path[index],
+          value = nested[key];
+
+      if (value != null) {
+        nested[key] = clone(Object(value));
+      }
+      nested = nested[key];
+    }
+    return result;
+  }
+
+  /**
+   * Converts `lodash` to an immutable auto-curried iteratee-first data-last
+   * version with conversion `options` applied.
+   *
+   * @param {Object} [options] The options object. See `baseConvert` for more details.
+   * @returns {Function} Returns the converted `lodash`.
+   */
+  function convertLib(options) {
+    return _.runInContext.convert(options)(undefined);
+  }
+
+  /**
+   * Create a converter function for `func` of `name`.
+   *
+   * @param {string} name The name of the function to convert.
+   * @param {Function} func The function to convert.
+   * @returns {Function} Returns the new converter function.
+   */
+  function createConverter(name, func) {
+    var oldOptions = options;
+    return function(options) {
+      var newUtil = isLib ? pristine : helpers,
+          newFunc = isLib ? pristine[name] : func,
+          newOptions = assign(assign({}, oldOptions), options);
+
+      return baseConvert(newUtil, name, newFunc, newOptions);
+    };
+  }
+
+  /**
+   * Creates a function that wraps `func` to invoke its iteratee, with up to `n`
+   * arguments, ignoring any additional arguments.
+   *
+   * @private
+   * @param {Function} func The function to cap iteratee arguments for.
+   * @param {number} n The arity cap.
+   * @returns {Function} Returns the new function.
+   */
+  function iterateeAry(func, n) {
+    return overArg(func, function(func) {
+      return typeof func == 'function' ? baseAry(func, n) : func;
+    });
+  }
+
+  /**
+   * Creates a function that wraps `func` to invoke its iteratee with arguments
+   * arranged according to the specified `indexes` where the argument value at
+   * the first index is provided as the first argument, the argument value at
+   * the second index is provided as the second argument, and so on.
+   *
+   * @private
+   * @param {Function} func The function to rearrange iteratee arguments for.
+   * @param {number[]} indexes The arranged argument indexes.
+   * @returns {Function} Returns the new function.
+   */
+  function iterateeRearg(func, indexes) {
+    return overArg(func, function(func) {
+      var n = indexes.length;
+      return baseArity(rearg(baseAry(func, n), indexes), n);
+    });
+  }
+
+  /**
+   * Creates a function that invokes `func` with its first argument passed
+   * thru `transform`.
+   *
+   * @private
+   * @param {Function} func The function to wrap.
+   * @param {...Function} transform The functions to transform the first argument.
+   * @returns {Function} Returns the new function.
+   */
+  function overArg(func, transform) {
+    return function() {
+      var length = arguments.length;
+      if (!length) {
+        return func();
+      }
+      var args = Array(length);
+      while (length--) {
+        args[length] = arguments[length];
+      }
+      var index = config.rearg ? 0 : (length - 1);
+      args[index] = transform(args[index]);
+      return func.apply(undefined, args);
+    };
+  }
+
+  /**
+   * Creates a function that wraps `func` and applys the conversions
+   * rules by `name`.
+   *
+   * @private
+   * @param {string} name The name of the function to wrap.
+   * @param {Function} func The function to wrap.
+   * @returns {Function} Returns the converted function.
+   */
+  function wrap(name, func) {
+    name = mapping.aliasToReal[name] || name;
+
+    var result,
+        wrapped = func,
+        wrapper = wrappers[name];
+
+    if (wrapper) {
+      wrapped = wrapper(func);
+    }
+    else if (config.immutable) {
+      if (mutateMap.array[name]) {
+        wrapped = immutWrap(func, cloneArray);
+      }
+      else if (mutateMap.object[name]) {
+        wrapped = immutWrap(func, createCloner(func));
+      }
+      else if (mutateMap.set[name]) {
+        wrapped = immutWrap(func, cloneByPath);
+      }
+    }
+    each(aryMethodKeys, function(aryKey) {
+      each(mapping.aryMethod[aryKey], function(otherName) {
+        if (name == otherName) {
+          var aryN = !isLib && mapping.iterateeAry[name],
+              reargIndexes = mapping.iterateeRearg[name],
+              spreadStart = mapping.methodSpread[name];
+
+          result = wrapped;
+          if (config.fixed && (forceFixed || !mapping.skipFixed[name])) {
+            result = spreadStart === undefined
+              ? ary(result, aryKey)
+              : spread(result, spreadStart);
+          }
+          if (config.rearg && aryKey > 1 && (forceRearg || !mapping.skipRearg[name])) {
+            result = rearg(result, mapping.methodRearg[name] || mapping.aryRearg[aryKey]);
+          }
+          if (config.cap) {
+            if (reargIndexes) {
+              result = iterateeRearg(result, reargIndexes);
+            } else if (aryN) {
+              result = iterateeAry(result, aryN);
+            }
+          }
+          if (forceCurry || (config.curry && aryKey > 1)) {
+            forceCurry  && console.log(forceCurry, name);
+            result = curry(result, aryKey);
+          }
+          return false;
+        }
+      });
+      return !result;
+    });
+
+    result || (result = wrapped);
+    if (result == func) {
+      result = forceCurry ? curry(result, 1) : function() {
+        return func.apply(this, arguments);
+      };
+    }
+    result.convert = createConverter(name, func);
+    if (mapping.placeholder[name]) {
+      setPlaceholder = true;
+      result.placeholder = func.placeholder = placeholder;
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  if (!isObj) {
+    return wrap(name, func);
+  }
+  var _ = func;
+
+  // Convert methods by ary cap.
+  var pairs = [];
+  each(aryMethodKeys, function(aryKey) {
+    each(mapping.aryMethod[aryKey], function(key) {
+      var func = _[mapping.remap[key] || key];
+      if (func) {
+        pairs.push([key, wrap(key, func)]);
+      }
+    });
+  });
+
+  // Convert remaining methods.
+  each(keys(_), function(key) {
+    var func = _[key];
+    if (typeof func == 'function') {
+      var length = pairs.length;
+      while (length--) {
+        if (pairs[length][0] == key) {
+          return;
+        }
+      }
+      func.convert = createConverter(key, func);
+      pairs.push([key, func]);
+    }
+  });
+
+  // Assign to `_` leaving `_.prototype` unchanged to allow chaining.
+  each(pairs, function(pair) {
+    _[pair[0]] = pair[1];
+  });
+
+  _.convert = convertLib;
+  if (setPlaceholder) {
+    _.placeholder = placeholder;
+  }
+  // Assign aliases.
+  each(keys(_), function(key) {
+    each(mapping.realToAlias[key] || [], function(alias) {
+      _[alias] = _[key];
+    });
+  });
+
+  return _;
+}
+
+module.exports = baseConvert;
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_convertBrowser.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_convertBrowser.js
new file mode 100644
index 0000000..1874a54
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_convertBrowser.js
@@ -0,0 +1,18 @@
+var baseConvert = require('./_baseConvert');
+
+/**
+ * Converts `lodash` to an immutable auto-curried iteratee-first data-last
+ * version with conversion `options` applied.
+ *
+ * @param {Function} lodash The lodash function to convert.
+ * @param {Object} [options] The options object. See `baseConvert` for more details.
+ * @returns {Function} Returns the converted `lodash`.
+ */
+function browserConvert(lodash, options) {
+  return baseConvert(lodash, lodash, options);
+}
+
+if (typeof _ == 'function') {
+  _ = browserConvert(_.runInContext());
+}
+module.exports = browserConvert;
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_mapping.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_mapping.js
new file mode 100644
index 0000000..18a3196
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/_mapping.js
@@ -0,0 +1,290 @@
+/** Used to map aliases to their real names. */
+exports.aliasToReal = {
+
+  // Lodash aliases.
+  'each': 'forEach',
+  'eachRight': 'forEachRight',
+  'entries': 'toPairs',
+  'entriesIn': 'toPairsIn',
+  'extend': 'assignIn',
+  'extendWith': 'assignInWith',
+  'first': 'head',
+
+  // Ramda aliases.
+  '__': 'placeholder',
+  'all': 'every',
+  'allPass': 'overEvery',
+  'always': 'constant',
+  'any': 'some',
+  'anyPass': 'overSome',
+  'apply': 'spread',
+  'assoc': 'set',
+  'assocPath': 'set',
+  'complement': 'negate',
+  'compose': 'flowRight',
+  'contains': 'includes',
+  'dissoc': 'unset',
+  'dissocPath': 'unset',
+  'equals': 'isEqual',
+  'identical': 'eq',
+  'init': 'initial',
+  'invertObj': 'invert',
+  'juxt': 'over',
+  'omitAll': 'omit',
+  'nAry': 'ary',
+  'path': 'get',
+  'pathEq': 'matchesProperty',
+  'pathOr': 'getOr',
+  'paths': 'at',
+  'pickAll': 'pick',
+  'pipe': 'flow',
+  'pluck': 'map',
+  'prop': 'get',
+  'propEq': 'matchesProperty',
+  'propOr': 'getOr',
+  'props': 'at',
+  'unapply': 'rest',
+  'unnest': 'flatten',
+  'useWith': 'overArgs',
+  'whereEq': 'filter',
+  'zipObj': 'zipObject'
+};
+
+/** Used to map ary to method names. */
+exports.aryMethod = {
+  '1': [
+    'attempt', 'castArray', 'ceil', 'create', 'curry', 'curryRight', 'floor',
+    'flow', 'flowRight', 'fromPairs', 'invert', 'iteratee', 'memoize', 'method',
+    'methodOf', 'mixin', 'over', 'overEvery', 'overSome', 'rest', 'reverse',
+    'round', 'runInContext', 'spread', 'template', 'trim', 'trimEnd', 'trimStart',
+    'uniqueId', 'words'
+  ],
+  '2': [
+    'add', 'after', 'ary', 'assign', 'assignIn', 'at', 'before', 'bind', 'bindAll',
+    'bindKey', 'chunk', 'cloneDeepWith', 'cloneWith', 'concat', 'countBy', 'curryN',
+    'curryRightN', 'debounce', 'defaults', 'defaultsDeep', 'delay', 'difference',
+    'divide', 'drop', 'dropRight', 'dropRightWhile', 'dropWhile', 'endsWith',
+    'eq', 'every', 'filter', 'find', 'find', 'findIndex', 'findKey', 'findLast',
+    'findLastIndex', 'findLastKey', 'flatMap', 'flatMapDeep', 'flattenDepth',
+    'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight',
+    'get', 'groupBy', 'gt', 'gte', 'has', 'hasIn', 'includes', 'indexOf',
+    'intersection', 'invertBy', 'invoke', 'invokeMap', 'isEqual', 'isMatch',
+    'join', 'keyBy', 'lastIndexOf', 'lt', 'lte', 'map', 'mapKeys', 'mapValues',
+    'matchesProperty', 'maxBy', 'meanBy', 'merge', 'minBy', 'multiply', 'nth',
+    'omit', 'omitBy', 'overArgs', 'pad', 'padEnd', 'padStart', 'parseInt',
+    'partial', 'partialRight', 'partition', 'pick', 'pickBy', 'pull', 'pullAll',
+    'pullAt', 'random', 'range', 'rangeRight', 'rearg', 'reject', 'remove',
+    'repeat', 'restFrom', 'result', 'sampleSize', 'some', 'sortBy', 'sortedIndex',
+    'sortedIndexOf', 'sortedLastIndex', 'sortedLastIndexOf', 'sortedUniqBy',
+    'split', 'spreadFrom', 'startsWith', 'subtract', 'sumBy', 'take', 'takeRight',
+    'takeRightWhile', 'takeWhile', 'tap', 'throttle', 'thru', 'times', 'trimChars',
+    'trimCharsEnd', 'trimCharsStart', 'truncate', 'union', 'uniqBy', 'uniqWith',
+    'unset', 'unzipWith', 'without', 'wrap', 'xor', 'zip', 'zipObject',
+    'zipObjectDeep'
+  ],
+  '3': [
+    'assignInWith', 'assignWith', 'clamp', 'differenceBy', 'differenceWith',
+    'getOr', 'inRange', 'intersectionBy', 'intersectionWith', 'invokeArgs',
+    'invokeArgsMap', 'isEqualWith', 'isMatchWith', 'flatMapDepth', 'mergeWith',
+    'orderBy', 'padChars', 'padCharsEnd', 'padCharsStart', 'pullAllBy',
+    'pullAllWith', 'reduce', 'reduceRight', 'replace', 'set', 'slice',
+    'sortedIndexBy', 'sortedLastIndexBy', 'transform', 'unionBy', 'unionWith',
+    'update', 'xorBy', 'xorWith', 'zipWith'
+  ],
+  '4': [
+    'fill', 'setWith', 'updateWith'
+  ]
+};
+
+/** Used to map ary to rearg configs. */
+exports.aryRearg = {
+  '2': [1, 0],
+  '3': [2, 0, 1],
+  '4': [3, 2, 0, 1]
+};
+
+/** Used to map method names to their iteratee ary. */
+exports.iterateeAry = {
+  'dropRightWhile': 1,
+  'dropWhile': 1,
+  'every': 1,
+  'filter': 1,
+  'find': 1,
+  'findIndex': 1,
+  'findKey': 1,
+  'findLast': 1,
+  'findLastIndex': 1,
+  'findLastKey': 1,
+  'flatMap': 1,
+  'flatMapDeep': 1,
+  'flatMapDepth': 1,
+  'forEach': 1,
+  'forEachRight': 1,
+  'forIn': 1,
+  'forInRight': 1,
+  'forOwn': 1,
+  'forOwnRight': 1,
+  'map': 1,
+  'mapKeys': 1,
+  'mapValues': 1,
+  'partition': 1,
+  'reduce': 2,
+  'reduceRight': 2,
+  'reject': 1,
+  'remove': 1,
+  'some': 1,
+  'takeRightWhile': 1,
+  'takeWhile': 1,
+  'times': 1,
+  'transform': 2
+};
+
+/** Used to map method names to iteratee rearg configs. */
+exports.iterateeRearg = {
+  'mapKeys': [1]
+};
+
+/** Used to map method names to rearg configs. */
+exports.methodRearg = {
+  'assignInWith': [1, 2, 0],
+  'assignWith': [1, 2, 0],
+  'getOr': [2, 1, 0],
+  'isEqualWith': [1, 2, 0],
+  'isMatchWith': [2, 1, 0],
+  'mergeWith': [1, 2, 0],
+  'padChars': [2, 1, 0],
+  'padCharsEnd': [2, 1, 0],
+  'padCharsStart': [2, 1, 0],
+  'pullAllBy': [2, 1, 0],
+  'pullAllWith': [2, 1, 0],
+  'setWith': [3, 1, 2, 0],
+  'sortedIndexBy': [2, 1, 0],
+  'sortedLastIndexBy': [2, 1, 0],
+  'updateWith': [3, 1, 2, 0],
+  'zipWith': [1, 2, 0]
+};
+
+/** Used to map method names to spread configs. */
+exports.methodSpread = {
+  'invokeArgs': 2,
+  'invokeArgsMap': 2,
+  'partial': 1,
+  'partialRight': 1,
+  'without': 1
+};
+
+/** Used to identify methods which mutate arrays or objects. */
+exports.mutate = {
+  'array': {
+    'fill': true,
+    'pull': true,
+    'pullAll': true,
+    'pullAllBy': true,
+    'pullAllWith': true,
+    'pullAt': true,
+    'remove': true,
+    'reverse': true
+  },
+  'object': {
+    'assign': true,
+    'assignIn': true,
+    'assignInWith': true,
+    'assignWith': true,
+    'defaults': true,
+    'defaultsDeep': true,
+    'merge': true,
+    'mergeWith': true
+  },
+  'set': {
+    'set': true,
+    'setWith': true,
+    'unset': true,
+    'update': true,
+    'updateWith': true
+  }
+};
+
+/** Used to track methods with placeholder support */
+exports.placeholder = {
+  'bind': true,
+  'bindKey': true,
+  'curry': true,
+  'curryRight': true,
+  'partial': true,
+  'partialRight': true
+};
+
+/** Used to map real names to their aliases. */
+exports.realToAlias = (function() {
+  var hasOwnProperty = Object.prototype.hasOwnProperty,
+      object = exports.aliasToReal,
+      result = {};
+
+  for (var key in object) {
+    var value = object[key];
+    if (hasOwnProperty.call(result, value)) {
+      result[value].push(key);
+    } else {
+      result[value] = [key];
+    }
+  }
+  return result;
+}());
+
+/** Used to map method names to other names. */
+exports.remap = {
+  'curryN': 'curry',
+  'curryRightN': 'curryRight',
+  'getOr': 'get',
+  'invokeArgs': 'invoke',
+  'invokeArgsMap': 'invokeMap',
+  'padChars': 'pad',
+  'padCharsEnd': 'padEnd',
+  'padCharsStart': 'padStart',
+  'restFrom': 'rest',
+  'spreadFrom': 'spread',
+  'trimChars': 'trim',
+  'trimCharsEnd': 'trimEnd',
+  'trimCharsStart': 'trimStart'
+};
+
+/** Used to track methods that skip fixing their arity. */
+exports.skipFixed = {
+  'castArray': true,
+  'flow': true,
+  'flowRight': true,
+  'iteratee': true,
+  'mixin': true,
+  'runInContext': true
+};
+
+/** Used to track methods that skip rearranging arguments. */
+exports.skipRearg = {
+  'add': true,
+  'assign': true,
+  'assignIn': true,
+  'bind': true,
+  'bindKey': true,
+  'concat': true,
+  'difference': true,
+  'divide': true,
+  'eq': true,
+  'gt': true,
+  'gte': true,
+  'isEqual': true,
+  'lt': true,
+  'lte': true,
+  'matchesProperty': true,
+  'merge': true,
+  'multiply': true,
+  'overArgs': true,
+  'partial': true,
+  'partialRight': true,
+  'random': true,
+  'range': true,
+  'rangeRight': true,
+  'subtract': true,
+  'without': true,
+  'zip': true,
+  'zipObject': true
+};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/placeholder.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/placeholder.js
new file mode 100644
index 0000000..1ce1739
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/fp/placeholder.js
@@ -0,0 +1,6 @@
+/**
+ * The default argument placeholder value for methods.
+ *
+ * @type {Object}
+ */
+module.exports = {};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/file.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/file.js
new file mode 100644
index 0000000..9f9016f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/file.js
@@ -0,0 +1,71 @@
+'use strict';
+
+var _ = require('lodash'),
+    fs = require('fs-extra'),
+    glob = require('glob'),
+    path = require('path');
+
+var minify = require('../common/minify.js');
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * Creates a [fs.copy](https://github.com/jprichardson/node-fs-extra#copy)
+ * function with `srcPath` and `destPath` partially applied.
+ *
+ * @memberOf file
+ * @param {string} srcPath The path of the file to copy.
+ * @param {string} destPath The path to copy the file to.
+ * @returns {Function} Returns the partially applied function.
+ */
+function copy(srcPath, destPath) {
+  return _.partial(fs.copy, srcPath, destPath);
+}
+
+/**
+ * Creates an object of compiled template and base name pairs that match `pattern`.
+ *
+ * @memberOf file
+ * @param {string} pattern The glob pattern to be match.
+ * @returns {Object} Returns the object of compiled templates.
+ */
+function globTemplate(pattern) {
+  return _.transform(glob.sync(pattern), function(result, filePath) {
+    var key = path.basename(filePath, path.extname(filePath));
+    result[key] = _.template(fs.readFileSync(filePath, 'utf8'));
+  }, {});
+}
+
+/**
+ * Creates a `minify` function with `srcPath` and `destPath` partially applied.
+ *
+ * @memberOf file
+ * @param {string} srcPath The path of the file to minify.
+ * @param {string} destPath The path to write the file to.
+ * @returns {Function} Returns the partially applied function.
+ */
+function min(srcPath, destPath) {
+  return _.partial(minify, srcPath, destPath);
+}
+
+/**
+ * Creates a [fs.writeFile](https://nodejs.org/api/fs.html#fs_fs_writefile_file_data_options_callback)
+ * function with `filePath` and `data` partially applied.
+ *
+ * @memberOf file
+ * @param {string} destPath The path to write the file to.
+ * @param {string} data The data to write to the file.
+ * @returns {Function} Returns the partially applied function.
+ */
+function write(destPath, data) {
+  return _.partial(fs.writeFile, destPath, data);
+}
+
+/*----------------------------------------------------------------------------*/
+
+module.exports = {
+  'copy': copy,
+  'globTemplate': globTemplate,
+  'min': min,
+  'write': write
+};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/mapping.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/mapping.js
new file mode 100644
index 0000000..332f5af
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/mapping.js
@@ -0,0 +1,9 @@
+'use strict';
+
+var _mapping = require('../../fp/_mapping'),
+    util = require('./util'),
+    Hash = util.Hash;
+
+/*----------------------------------------------------------------------------*/
+
+module.exports = new Hash(_mapping);
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/minify.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/minify.js
new file mode 100644
index 0000000..7a0082d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/minify.js
@@ -0,0 +1,39 @@
+'use strict';
+
+var _ = require('lodash'),
+    fs = require('fs-extra'),
+    uglify = require('uglify-js');
+
+var uglifyOptions = require('./uglify.options');
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * Asynchronously minifies the file at `srcPath`, writes it to `destPath`, and
+ * invokes `callback` upon completion. The callback is invoked with one argument:
+ * (error).
+ *
+ * If unspecified, `destPath` is `srcPath` with an extension of `.min.js`. For
+ * example, a `srcPath` of `path/to/foo.js` would have a `destPath` of `path/to/foo.min.js`.
+ *
+ * @param {string} srcPath The path of the file to minify.
+ * @param {string} [destPath] The path to write the file to.
+ * @param {Function} callback The function invoked upon completion.
+ * @param {Object} [option] The UglifyJS options object.
+ */
+function minify(srcPath, destPath, callback, options) {
+  if (_.isFunction(destPath)) {
+    if (_.isObject(callback)) {
+      options = callback;
+    }
+    callback = destPath;
+    destPath = undefined;
+  }
+  if (!destPath) {
+    destPath = srcPath.replace(/(?=\.js$)/, '.min');
+  }
+  var output = uglify.minify(srcPath, _.defaults(options || {}, uglifyOptions));
+  fs.writeFile(destPath, output.code, 'utf-8', callback);
+}
+
+module.exports = minify;
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/uglify.options.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/uglify.options.js
new file mode 100644
index 0000000..af0ff43
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/uglify.options.js
@@ -0,0 +1,23 @@
+'use strict';
+
+/**
+ * The UglifyJS options object for
+ * [compress](https://github.com/mishoo/UglifyJS2#compressor-options),
+ * [mangle](https://github.com/mishoo/UglifyJS2#mangler-options), and
+ * [output](https://github.com/mishoo/UglifyJS2#beautifier-options) options.
+ */
+module.exports = {
+  'compress': {
+    'pure_getters': true,
+    'unsafe': true,
+    'warnings': false
+  },
+  'mangle': {
+    'except': ['define']
+  },
+  'output': {
+    'ascii_only': true,
+    'comments': /^!|@cc_on|@license|@preserve/i,
+    'max_line_len': 500
+  }
+};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/util.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/util.js
new file mode 100644
index 0000000..6445186
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/common/util.js
@@ -0,0 +1,27 @@
+'use strict';
+
+var _ = require('lodash');
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * Creates a hash object. If a `properties` object is provided, its own
+ * enumerable properties are assigned to the created object.
+ *
+ * @memberOf util
+ * @param {Object} [properties] The properties to assign to the object.
+ * @returns {Object} Returns the new hash object.
+ */
+function Hash(properties) {
+  return _.transform(properties, function(result, value, key) {
+    result[key] = (_.isPlainObject(value) && !(value instanceof Hash))
+      ? new Hash(value)
+      : value;
+  }, this);
+}
+
+Hash.prototype = Object.create(null);
+
+module.exports = {
+  'Hash': Hash
+};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-dist.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-dist.js
new file mode 100644
index 0000000..bad62d2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-dist.js
@@ -0,0 +1,55 @@
+'use strict';
+
+var _ = require('lodash'),
+    async = require('async'),
+    path = require('path'),
+    webpack = require('webpack');
+
+var file = require('../common/file');
+
+var basePath = path.join(__dirname, '..', '..'),
+    distPath = path.join(basePath, 'dist'),
+    fpPath = path.join(basePath, 'fp'),
+    filename = 'lodash.fp.js';
+
+var fpConfig = {
+  'entry': path.join(fpPath, '_convertBrowser.js'),
+  'output': {
+    'path': distPath,
+    'filename': filename,
+    'library': 'fp',
+    'libraryTarget': 'umd'
+  },
+  'plugins': [
+    new webpack.optimize.OccurenceOrderPlugin,
+    new webpack.optimize.DedupePlugin
+  ]
+};
+
+var mappingConfig = {
+  'entry': path.join(fpPath, '_mapping.js'),
+  'output': {
+    'path': distPath,
+    'filename': 'mapping.fp.js',
+    'library': 'mapping',
+    'libraryTarget': 'umd'
+  }
+};
+
+/*----------------------------------------------------------------------------*/
+
+function onComplete(error) {
+  if (error) {
+    throw error;
+  }
+}
+
+function build() {
+  async.series([
+    _.partial(webpack, mappingConfig),
+    _.partial(webpack, fpConfig),
+    file.min(path.join(distPath, filename))
+  ], onComplete);
+}
+
+build();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-doc.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-doc.js
new file mode 100644
index 0000000..02800bc
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-doc.js
@@ -0,0 +1,65 @@
+'use strict';
+
+var _ = require('lodash'),
+    fs = require('fs-extra'),
+    path = require('path');
+
+var file = require('../common/file'),
+    mapping = require('../common/mapping');
+
+var templatePath = path.join(__dirname, 'template/doc'),
+    template = file.globTemplate(path.join(templatePath, '*.jst'));
+
+var argNames = ['a', 'b', 'c', 'd'];
+
+var templateData = {
+  'mapping': mapping,
+  'toArgOrder': toArgOrder,
+  'toFuncList': toFuncList
+};
+
+function toArgOrder(array) {
+  var reordered = [];
+  _.each(array, function(newIndex, index) {
+    reordered[newIndex] = argNames[index];
+  });
+  return '`(' + reordered.join(', ') + ')`';
+}
+
+function toFuncList(array) {
+  var chunks = _.chunk(array.slice().sort(), 5),
+      lastChunk = _.last(chunks),
+      last = lastChunk ? lastChunk.pop() : undefined;
+
+  chunks = _.reject(chunks, _.isEmpty);
+  lastChunk = _.last(chunks);
+
+  var result = '`' + _.map(chunks, function(chunk) {
+    return chunk.join('`, `') + '`';
+  }).join(',\n`');
+
+  if (last == null) {
+    return result;
+  }
+  if (_.size(chunks) > 1 || _.size(lastChunk) > 1) {
+    result += ',';
+  }
+  result += ' &';
+  result += _.size(lastChunk) < 5 ? ' ' : '\n';
+  return result + '`' + last + '`';
+}
+
+/*----------------------------------------------------------------------------*/
+
+function onComplete(error) {
+  if (error) {
+    throw error;
+  }
+}
+
+function build(target) {
+  target = path.resolve(target);
+  fs.writeFile(target, template.wiki(templateData), onComplete);
+}
+
+build(_.last(process.argv));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-modules.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-modules.js
new file mode 100644
index 0000000..43902e0
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/build-modules.js
@@ -0,0 +1,120 @@
+'use strict';
+
+var _ = require('lodash'),
+    async = require('async'),
+    glob = require('glob'),
+    path = require('path');
+
+var file = require('../common/file'),
+    mapping = require('../common/mapping');
+
+var templatePath = path.join(__dirname, 'template/modules'),
+    template = file.globTemplate(path.join(templatePath, '*.jst'));
+
+var aryMethods = _.union(
+  mapping.aryMethod[1],
+  mapping.aryMethod[2],
+  mapping.aryMethod[3],
+  mapping.aryMethod[4]
+);
+
+var categories = [
+  'array',
+  'collection',
+  'date',
+  'function',
+  'lang',
+  'math',
+  'number',
+  'object',
+  'seq',
+  'string',
+  'util'
+];
+
+var ignored = [
+  '_*.js',
+  'core.js',
+  'core.min.js',
+  'fp.js',
+  'index.js',
+  'lodash.js',
+  'lodash.min.js'
+];
+
+function isAlias(funcName) {
+  return _.has(mapping.aliasToReal, funcName);
+}
+
+function isCategory(funcName) {
+  return _.includes(categories, funcName);
+}
+
+function isThru(funcName) {
+  return !_.includes(aryMethods, funcName);
+}
+
+function getTemplate(moduleName) {
+  var data = {
+    'name': _.result(mapping.aliasToReal, moduleName, moduleName),
+    'mapping': mapping
+  };
+
+  if (isAlias(moduleName)) {
+    return template.alias(data);
+  }
+  if (isCategory(moduleName)) {
+    return template.category(data);
+  }
+  if (isThru(moduleName)) {
+    return template.thru(data);
+  }
+  return template.module(data);
+}
+
+/*----------------------------------------------------------------------------*/
+
+function onComplete(error) {
+  if (error) {
+    throw error;
+  }
+}
+
+function build(target) {
+  target = path.resolve(target);
+
+  var fpPath = path.join(target, 'fp');
+
+  // Glob existing lodash module paths.
+  var modulePaths = glob.sync(path.join(target, '*.js'), {
+    'nodir': true,
+    'ignore': ignored.map(function(filename) {
+      return path.join(target, filename);
+    })
+  });
+
+  // Add FP alias and remapped module paths.
+  _.each([mapping.aliasToReal, mapping.remap], function(data) {
+    _.forOwn(data, function(realName, alias) {
+      var modulePath = path.join(target, alias + '.js');
+      if (!_.includes(modulePaths, modulePath)) {
+        modulePaths.push(modulePath);
+      }
+    });
+  });
+
+  var actions = modulePaths.map(function(modulePath) {
+    var moduleName = path.basename(modulePath, '.js');
+    return file.write(path.join(fpPath, moduleName + '.js'), getTemplate(moduleName));
+  });
+
+  actions.unshift(file.copy(path.join(__dirname, '../../fp'), fpPath));
+  actions.push(file.write(path.join(fpPath, '_falseOptions.js'), template._falseOptions()));
+  actions.push(file.write(path.join(fpPath, '_util.js'), template._util()));
+  actions.push(file.write(path.join(target, 'fp.js'), template.fp()));
+  actions.push(file.write(path.join(fpPath, 'convert.js'), template.convert()));
+
+  async.series(actions, onComplete);
+}
+
+build(_.last(process.argv));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/doc/wiki.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/doc/wiki.jst
new file mode 100644
index 0000000..188302f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/doc/wiki.jst
@@ -0,0 +1,220 @@
+## lodash/fp
+
+The `lodash/fp` module is an instance of `lodash` with its methods wrapped to
+produce immutable auto-curried iteratee-first data-last methods.
+
+## Installation
+
+In a browser:
+```html
+<script src='path/to/lodash.js'></script>
+<script src='path/to/lodash.fp.js'></script>
+<script>
+// Loading `lodash.fp.js` converts `_` to its fp variant.
+_.defaults({ 'a': 2, 'b': 2 })({ 'a': 1 });
+// → { 'a: 1, 'b': 2 }
+
+// Use `noConflict` to restore the pre-fp variant.
+var fp = _.noConflict();
+
+_.defaults({ 'a': 1 }, { 'a': 2, 'b': 2 });
+// → { 'a: 1, 'b': 2 }
+fp.defaults({ 'a': 2, 'b': 2 })({ 'a': 1 });
+// → { 'a: 1, 'b': 2 }
+</script>
+```
+
+In Node.js:
+```js
+// Load the fp build.
+var fp = require('lodash/fp');
+
+// Load a method category.
+var object = require('lodash/fp/object');
+
+// Load a single method for smaller builds with browserify/rollup/webpack.
+var extend = require('lodash/fp/extend');
+```
+
+## Mapping
+
+Immutable auto-curried iteratee-first data-last methods sound great, but what
+does that really mean for each method? Below is a breakdown of the mapping used
+to convert each method.
+
+#### Capped Iteratee Arguments
+
+Iteratee arguments are capped to avoid gotchas with variadic iteratees.
+```js
+// The `lodash/map` iteratee receives three arguments:
+// (value, index|key, collection)
+_.map(['6', '8', '10'], parseInt);
+// → [6, NaN, 2]
+
+// The `lodash/fp/map` iteratee is capped at one argument:
+// (value)
+fp.map(parseInt)(['6', '8', '10']);
+// → [6, 8, 10]
+```
+
+Methods that cap iteratees to one argument:<br>
+<%= toFuncList(_.keys(_.pickBy(mapping.iterateeAry, _.partial(_.eq, _, 1)))) %>
+
+Methods that cap iteratees to two arguments:<br>
+<%= toFuncList(_.keys(_.pickBy(mapping.iterateeAry, _.partial(_.eq, _, 2)))) %>
+
+The iteratee of `mapKeys` is invoked with one argument: (key)
+
+#### Fixed Arity
+
+Methods have fixed arities to support auto-currying.
+```js
+// `lodash/padStart` accepts an optional `chars` param.
+_.padStart('a', 3, '-')
+// → '--a'
+
+// `lodash/fp/padStart` does not.
+fp.padStart(3)('a');
+// → '  a'
+fp.padCharsStart('-')(3)('a');
+// → '--a'
+```
+
+Methods with a fixed arity of one:<br>
+<%= toFuncList(_.difference(mapping.aryMethod[1], _.keys(mapping.skipFixed))) %>
+
+Methods with a fixed arity of two:<br>
+<%= toFuncList(_.difference(mapping.aryMethod[2], _.keys(mapping.skipFixed))) %>
+
+Methods with a fixed arity of three:<br>
+<%= toFuncList(_.difference(mapping.aryMethod[3], _.keys(mapping.skipFixed))) %>
+
+Methods with a fixed arity of four:<br>
+<%= toFuncList(_.difference(mapping.aryMethod[4], _.keys(mapping.skipFixed))) %>
+
+#### Rearranged Arguments
+
+Method arguments are rearranged to make composition easier.
+```js
+// `lodash/filter` is data-first iteratee-last:
+// (collection, iteratee)
+var compact = _.partial(_.filter, _, Boolean);
+compact(['a', null, 'c']);
+// → ['a', 'c']
+
+// `lodash/fp/filter` is iteratee-first data-last:
+// (iteratee, collection)
+var compact = fp.filter(Boolean);
+compact(['a', null, 'c']);
+// → ['a', 'c']
+```
+
+##### Most methods follow these rules
+
+A fixed arity of two has an argument order of:<br>
+<%= toArgOrder(mapping.aryRearg[2]) %>
+
+A fixed arity of three has an argument order of:<br>
+<%= toArgOrder(mapping.aryRearg[3]) %>
+
+A fixed arity of four has an argument order of:<br>
+<%= toArgOrder(mapping.aryRearg[4]) %>
+
+##### Exceptions to the rules
+
+Methods that accept an array of arguments as their second parameter:<br>
+<%= toFuncList(_.keys(mapping.methodSpread)) %>
+
+Methods with unchanged argument orders:<br>
+<%= toFuncList(_.keys(mapping.skipRearg)) %>
+
+Methods with custom argument orders:<br>
+<%= _.map(_.keys(mapping.methodRearg), function(methodName) {
+  var orders = mapping.methodRearg[methodName];
+  return ' * `_.' + methodName + '` has an order of ' + toArgOrder(orders);
+}).join('\n') %>
+
+#### New Methods
+
+Not all variadic methods have corresponding new method variants. Feel free to
+[request](https://github.com/lodash/lodash/blob/master/.github/CONTRIBUTING.md#feature-requests)
+any additions.
+
+Methods created to accommodate Lodash’s variadic methods:<br>
+<%= toFuncList(_.keys(mapping.remap)) %>
+
+#### Aliases
+
+There are <%= _.size(mapping.aliasToReal) %> method aliases:<br>
+<%= _.map(_.keys(mapping.aliasToReal).sort(), function(alias) {
+  var realName = mapping.aliasToReal[alias];
+  return ' * `_.' + alias + '` is an alias of `_.' + realName + '`';
+}).join('\n') %>
+
+## Placeholders
+
+The placeholder argument, which defaults to `_`, may be used to fill in method
+arguments in a different order. Placeholders are filled by the first available
+arguments of the curried returned function.
+```js
+// The equivalent of `2 > 5`.
+_.gt(2)(5);
+// → false
+
+// The equivalent of `_.gt(5, 2)` or `5 > 2`.
+_.gt(_, 2)(5);
+// → true
+```
+
+## Chaining
+
+The `lodash/fp` module **does not** convert chain sequence methods. See
+[Izaak Schroeder’s article](https://medium.com/making-internets/why-using-chain-is-a-mistake-9bc1f80d51ba)
+on using functional composition as an alternative to method chaining.
+
+## Convert
+
+Although `lodash/fp` & its method modules come pre-converted, there are times
+when you may want to customize the conversion. That’s when the `convert` method
+comes in handy.
+```js
+// Every option is `true` by default.
+var _fp = fp.convert({
+  // Specify capping iteratee arguments.
+  'cap': true,
+  // Specify currying.
+  'curry': true,
+  // Specify fixed arity.
+  'fixed': true,
+  // Specify immutable operations.
+  'immutable': true,
+  // Specify rearranging arguments.
+  'rearg': true
+});
+
+// The `convert` method is available on each method too.
+var mapValuesWithKey = fp.mapValues.convert({ 'cap': false });
+
+// Here’s an example of disabling iteratee argument caps to access the `key` param.
+mapValuesWithKey(function(value, key) {
+  return key == 'a' ? -1 : value;
+})({ 'a': 1, 'b': 1 });
+// => { 'a': -1, 'b': 1 }
+```
+
+Manual conversions are also possible with the `convert` module.
+```js
+var convert = require('lodash/fp/convert');
+
+// Convert by name.
+var assign = convert('assign', require('lodash.assign'));
+
+// Convert by object.
+var fp = convert({
+  'assign': require('lodash.assign'),
+  'chunk': require('lodash.chunk')
+});
+
+// Convert by `lodash` instance.
+var fp = convert(lodash.runInContext());
+```
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/_falseOptions.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/_falseOptions.jst
new file mode 100644
index 0000000..773235e
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/_falseOptions.jst
@@ -0,0 +1,7 @@
+module.exports = {
+  'cap': false,
+  'curry': false,
+  'fixed': false,
+  'immutable': false,
+  'rearg': false
+};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/_util.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/_util.jst
new file mode 100644
index 0000000..d450396
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/_util.jst
@@ -0,0 +1,14 @@
+module.exports = {
+  'ary': require('../ary'),
+  'assign': require('../_baseAssign'),
+  'clone': require('../clone'),
+  'curry': require('../curry'),
+  'forEach': require('../_arrayEach'),
+  'isArray': require('../isArray'),
+  'isFunction': require('../isFunction'),
+  'iteratee': require('../iteratee'),
+  'keys': require('../_baseKeys'),
+  'rearg': require('../rearg'),
+  'spread': require('../spread'),
+  'toPath': require('../toPath')
+};
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/alias.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/alias.jst
new file mode 100644
index 0000000..6d72710
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/alias.jst
@@ -0,0 +1 @@
+module.exports = require('./<%= name %>');
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/category.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/category.jst
new file mode 100644
index 0000000..62c2db8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/category.jst
@@ -0,0 +1,2 @@
+var convert = require('./convert');
+module.exports = convert(require('../<%= name %>'));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/convert.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/convert.jst
new file mode 100644
index 0000000..4795dc4
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/convert.jst
@@ -0,0 +1,18 @@
+var baseConvert = require('./_baseConvert'),
+    util = require('./_util');
+
+/**
+ * Converts `func` of `name` to an immutable auto-curried iteratee-first data-last
+ * version with conversion `options` applied. If `name` is an object its methods
+ * will be converted.
+ *
+ * @param {string} name The name of the function to wrap.
+ * @param {Function} [func] The function to wrap.
+ * @param {Object} [options] The options object. See `baseConvert` for more details.
+ * @returns {Function|Object} Returns the converted function or object.
+ */
+function convert(name, func, options) {
+  return baseConvert(util, name, func, options);
+}
+
+module.exports = convert;
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/fp.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/fp.jst
new file mode 100644
index 0000000..e372dbb
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/fp.jst
@@ -0,0 +1,2 @@
+var _ = require('./lodash.min').runInContext();
+module.exports = require('./fp/_baseConvert')(_, _);
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/module.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/module.jst
new file mode 100644
index 0000000..289bd2b
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/module.jst
@@ -0,0 +1,5 @@
+var convert = require('./convert'),
+    func = convert('<%= name %>', require('../<%= _.result(mapping.remap, name, name) %>'));
+
+func.placeholder = require('./placeholder');
+module.exports = func;
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/thru.jst b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/thru.jst
new file mode 100644
index 0000000..5bc1a7b
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/fp/template/modules/thru.jst
@@ -0,0 +1,5 @@
+var convert = require('./convert'),
+    func = convert('<%= name %>', require('../<%= _.result(mapping.remap, name, name) %>'), require('./_falseOptions'));
+
+func.placeholder = require('./placeholder');
+module.exports = func;
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-dist.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-dist.js
new file mode 100644
index 0000000..14b3fd3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-dist.js
@@ -0,0 +1,30 @@
+'use strict';
+
+var async = require('async'),
+    path = require('path');
+
+var file = require('../common/file');
+
+var basePath = path.join(__dirname, '..', '..'),
+    distPath = path.join(basePath, 'dist'),
+    filename = 'lodash.js';
+
+var baseLodash = path.join(basePath, filename),
+    distLodash = path.join(distPath, filename);
+
+/*----------------------------------------------------------------------------*/
+
+function onComplete(error) {
+  if (error) {
+    throw error;
+  }
+}
+
+function build() {
+  async.series([
+    file.copy(baseLodash, distLodash),
+    file.min(distLodash)
+  ], onComplete);
+}
+
+build();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-doc.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-doc.js
new file mode 100644
index 0000000..9756954
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-doc.js
@@ -0,0 +1,60 @@
+'use strict';
+
+var _ = require('lodash'),
+    docdown = require('docdown'),
+    fs = require('fs-extra'),
+    path = require('path');
+
+var basePath = path.join(__dirname, '..', '..'),
+    docPath = path.join(basePath, 'doc'),
+    readmePath = path.join(docPath, 'README.md');
+
+var pkg = require('../../package.json'),
+    version = pkg.version;
+
+var config = {
+  'base': {
+    'entryLinks': [
+      '<% if (name == "templateSettings" || !/^(?:methods|properties|seq)$/i.test(category)) {' +
+      'print("[&#x24C3;](https://www.npmjs.com/package/lodash." + name.toLowerCase() + " \\"See the npm package\\")")' +
+      '} %>'
+    ],
+    'path': path.join(basePath, 'lodash.js'),
+    'title': '<a href="https://lodash.com/">lodash</a> <span>v' + version + '</span>',
+    'toc': 'categories',
+    'url': 'https://github.com/lodash/lodash/blob/' + version + '/lodash.js'
+  },
+  'github': {
+    'hash': 'github'
+  },
+  'site': {
+    'tocLink': '#docs'
+  }
+};
+
+function postprocess(string) {
+  // Fix docdown bugs.
+  return string
+    // Repair the default value of `chars`.
+    // See https://github.com/eslint/doctrine/issues/157 for more details.
+    .replace(/\bchars=''/g, "chars=' '")
+    // Wrap symbol property identifiers in brackets.
+    .replace(/\.(Symbol\.(?:[a-z]+[A-Z]?)+)/g, '[$1]');
+}
+
+/*----------------------------------------------------------------------------*/
+
+function onComplete(error) {
+  if (error) {
+    throw error;
+  }
+}
+
+function build(type) {
+  var options = _.defaults({}, config.base, config[type]),
+      markdown = docdown(options);
+
+  fs.writeFile(readmePath, postprocess(markdown), onComplete);
+}
+
+build(_.last(process.argv));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-modules.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-modules.js
new file mode 100644
index 0000000..5d89e0d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lib/main/build-modules.js
@@ -0,0 +1,34 @@
+'use strict';
+
+var _ = require('lodash'),
+    async = require('async'),
+    path = require('path');
+
+var file = require('../common/file');
+
+var basePath = path.join(__dirname, '..', '..'),
+    distPath = path.join(basePath, 'dist');
+
+var filePairs = [
+  [path.join(distPath, 'lodash.core.js'), 'core.js'],
+  [path.join(distPath, 'lodash.core.min.js'), 'core.min.js'],
+  [path.join(distPath, 'lodash.min.js'), 'lodash.min.js']
+];
+
+/*----------------------------------------------------------------------------*/
+
+function onComplete(error) {
+  if (error) {
+    throw error;
+  }
+}
+
+function build(target) {
+  var actions = _.map(filePairs, function(pair) {
+    return file.copy(pair[0], path.join(target, pair[1]));
+  });
+
+  async.series(actions, onComplete);
+}
+
+build(_.last(process.argv));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/lodash.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/lodash.js
new file mode 100644
index 0000000..9f472f6
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/lodash.js
@@ -0,0 +1,16148 @@
+/**
+ * @license
+ * lodash 4.11.2 <https://lodash.com/>
+ * Copyright jQuery Foundation and other contributors <https://jquery.org/>
+ * Released under MIT license <https://lodash.com/license>
+ * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE>
+ * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+ */
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the semantic version number. */
+  var VERSION = '4.11.2';
+
+  /** Used as the size to enable large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Used as the `TypeError` message for "Functions" methods. */
+  var FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used to stand-in for `undefined` hash values. */
+  var HASH_UNDEFINED = '__lodash_hash_undefined__';
+
+  /** Used as the internal argument placeholder. */
+  var PLACEHOLDER = '__lodash_placeholder__';
+
+  /** Used to compose bitmasks for wrapper metadata. */
+  var BIND_FLAG = 1,
+      BIND_KEY_FLAG = 2,
+      CURRY_BOUND_FLAG = 4,
+      CURRY_FLAG = 8,
+      CURRY_RIGHT_FLAG = 16,
+      PARTIAL_FLAG = 32,
+      PARTIAL_RIGHT_FLAG = 64,
+      ARY_FLAG = 128,
+      REARG_FLAG = 256,
+      FLIP_FLAG = 512;
+
+  /** Used to compose bitmasks for comparison styles. */
+  var UNORDERED_COMPARE_FLAG = 1,
+      PARTIAL_COMPARE_FLAG = 2;
+
+  /** Used as default options for `_.truncate`. */
+  var DEFAULT_TRUNC_LENGTH = 30,
+      DEFAULT_TRUNC_OMISSION = '...';
+
+  /** Used to detect hot functions by number of calls within a span of milliseconds. */
+  var HOT_COUNT = 150,
+      HOT_SPAN = 16;
+
+  /** Used to indicate the type of lazy iteratees. */
+  var LAZY_FILTER_FLAG = 1,
+      LAZY_MAP_FLAG = 2,
+      LAZY_WHILE_FLAG = 3;
+
+  /** Used as references for various `Number` constants. */
+  var INFINITY = 1 / 0,
+      MAX_SAFE_INTEGER = 9007199254740991,
+      MAX_INTEGER = 1.7976931348623157e+308,
+      NAN = 0 / 0;
+
+  /** Used as references for the maximum length and index of an array. */
+  var MAX_ARRAY_LENGTH = 4294967295,
+      MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1,
+      HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1;
+
+  /** `Object#toString` result references. */
+  var argsTag = '[object Arguments]',
+      arrayTag = '[object Array]',
+      boolTag = '[object Boolean]',
+      dateTag = '[object Date]',
+      errorTag = '[object Error]',
+      funcTag = '[object Function]',
+      genTag = '[object GeneratorFunction]',
+      mapTag = '[object Map]',
+      numberTag = '[object Number]',
+      objectTag = '[object Object]',
+      promiseTag = '[object Promise]',
+      regexpTag = '[object RegExp]',
+      setTag = '[object Set]',
+      stringTag = '[object String]',
+      symbolTag = '[object Symbol]',
+      weakMapTag = '[object WeakMap]',
+      weakSetTag = '[object WeakSet]';
+
+  var arrayBufferTag = '[object ArrayBuffer]',
+      dataViewTag = '[object DataView]',
+      float32Tag = '[object Float32Array]',
+      float64Tag = '[object Float64Array]',
+      int8Tag = '[object Int8Array]',
+      int16Tag = '[object Int16Array]',
+      int32Tag = '[object Int32Array]',
+      uint8Tag = '[object Uint8Array]',
+      uint8ClampedTag = '[object Uint8ClampedArray]',
+      uint16Tag = '[object Uint16Array]',
+      uint32Tag = '[object Uint32Array]';
+
+  /** 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 HTML entities and HTML characters. */
+  var reEscapedHtml = /&(?:amp|lt|gt|quot|#39|#96);/g,
+      reUnescapedHtml = /[&<>"'`]/g,
+      reHasEscapedHtml = RegExp(reEscapedHtml.source),
+      reHasUnescapedHtml = RegExp(reUnescapedHtml.source);
+
+  /** Used to match template delimiters. */
+  var reEscape = /<%-([\s\S]+?)%>/g,
+      reEvaluate = /<%([\s\S]+?)%>/g,
+      reInterpolate = /<%=([\s\S]+?)%>/g;
+
+  /** Used to match property names within property paths. */
+  var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/,
+      reIsPlainProp = /^\w*$/,
+      rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]/g;
+
+  /**
+   * Used to match `RegExp`
+   * [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns).
+   */
+  var reRegExpChar = /[\\^$.*+?()[\]{}|]/g,
+      reHasRegExpChar = RegExp(reRegExpChar.source);
+
+  /** Used to match leading and trailing whitespace. */
+  var reTrim = /^\s+|\s+$/g,
+      reTrimStart = /^\s+/,
+      reTrimEnd = /\s+$/;
+
+  /** Used to match non-compound words composed of alphanumeric characters. */
+  var reBasicWord = /[a-zA-Z0-9]+/g;
+
+  /** Used to match backslashes in property paths. */
+  var reEscapeChar = /\\(\\)?/g;
+
+  /**
+   * Used to match
+   * [ES template delimiters](http://ecma-international.org/ecma-262/6.0/#sec-template-literal-lexical-components).
+   */
+  var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g;
+
+  /** Used to match `RegExp` flags from their coerced string values. */
+  var reFlags = /\w*$/;
+
+  /** Used to detect hexadecimal string values. */
+  var reHasHexPrefix = /^0x/i;
+
+  /** Used to detect bad signed hexadecimal string values. */
+  var reIsBadHex = /^[-+]0x[0-9a-f]+$/i;
+
+  /** Used to detect binary string values. */
+  var reIsBinary = /^0b[01]+$/i;
+
+  /** Used to detect host constructors (Safari). */
+  var reIsHostCtor = /^\[object .+?Constructor\]$/;
+
+  /** Used to detect octal string values. */
+  var reIsOctal = /^0o[0-7]+$/i;
+
+  /** Used to detect unsigned integer values. */
+  var reIsUint = /^(?:0|[1-9]\d*)$/;
+
+  /** Used to match latin-1 supplementary letters (excluding mathematical operators). */
+  var reLatin1 = /[\xc0-\xd6\xd8-\xde\xdf-\xf6\xf8-\xff]/g;
+
+  /** Used to ensure capturing order of template delimiters. */
+  var reNoMatch = /($^)/;
+
+  /** Used to match unescaped characters in compiled string literals. */
+  var reUnescapedString = /['\n\r\u2028\u2029\\]/g;
+
+  /** Used to compose unicode character classes. */
+  var rsAstralRange = '\\ud800-\\udfff',
+      rsComboMarksRange = '\\u0300-\\u036f\\ufe20-\\ufe23',
+      rsComboSymbolsRange = '\\u20d0-\\u20f0',
+      rsDingbatRange = '\\u2700-\\u27bf',
+      rsLowerRange = 'a-z\\xdf-\\xf6\\xf8-\\xff',
+      rsMathOpRange = '\\xac\\xb1\\xd7\\xf7',
+      rsNonCharRange = '\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf',
+      rsPunctuationRange = '\\u2000-\\u206f',
+      rsSpaceRange = ' \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000',
+      rsUpperRange = 'A-Z\\xc0-\\xd6\\xd8-\\xde',
+      rsVarRange = '\\ufe0e\\ufe0f',
+      rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange;
+
+  /** Used to compose unicode capture groups. */
+  var rsApos = "['\u2019]",
+      rsAstral = '[' + rsAstralRange + ']',
+      rsBreak = '[' + rsBreakRange + ']',
+      rsCombo = '[' + rsComboMarksRange + rsComboSymbolsRange + ']',
+      rsDigits = '\\d+',
+      rsDingbat = '[' + rsDingbatRange + ']',
+      rsLower = '[' + rsLowerRange + ']',
+      rsMisc = '[^' + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + ']',
+      rsFitz = '\\ud83c[\\udffb-\\udfff]',
+      rsModifier = '(?:' + rsCombo + '|' + rsFitz + ')',
+      rsNonAstral = '[^' + rsAstralRange + ']',
+      rsRegional = '(?:\\ud83c[\\udde6-\\uddff]){2}',
+      rsSurrPair = '[\\ud800-\\udbff][\\udc00-\\udfff]',
+      rsUpper = '[' + rsUpperRange + ']',
+      rsZWJ = '\\u200d';
+
+  /** Used to compose unicode regexes. */
+  var rsLowerMisc = '(?:' + rsLower + '|' + rsMisc + ')',
+      rsUpperMisc = '(?:' + rsUpper + '|' + rsMisc + ')',
+      rsOptLowerContr = '(?:' + rsApos + '(?:d|ll|m|re|s|t|ve))?',
+      rsOptUpperContr = '(?:' + rsApos + '(?:D|LL|M|RE|S|T|VE))?',
+      reOptMod = rsModifier + '?',
+      rsOptVar = '[' + rsVarRange + ']?',
+      rsOptJoin = '(?:' + rsZWJ + '(?:' + [rsNonAstral, rsRegional, rsSurrPair].join('|') + ')' + rsOptVar + reOptMod + ')*',
+      rsSeq = rsOptVar + reOptMod + rsOptJoin,
+      rsEmoji = '(?:' + [rsDingbat, rsRegional, rsSurrPair].join('|') + ')' + rsSeq,
+      rsSymbol = '(?:' + [rsNonAstral + rsCombo + '?', rsCombo, rsRegional, rsSurrPair, rsAstral].join('|') + ')';
+
+  /** Used to match apostrophes. */
+  var reApos = RegExp(rsApos, 'g');
+
+  /**
+   * Used to match [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks) and
+   * [combining diacritical marks for symbols](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks_for_Symbols).
+   */
+  var reComboMark = RegExp(rsCombo, 'g');
+
+  /** Used to match [string symbols](https://mathiasbynens.be/notes/javascript-unicode). */
+  var reComplexSymbol = RegExp(rsFitz + '(?=' + rsFitz + ')|' + rsSymbol + rsSeq, 'g');
+
+  /** Used to match complex or compound words. */
+  var reComplexWord = RegExp([
+    rsUpper + '?' + rsLower + '+' + rsOptLowerContr + '(?=' + [rsBreak, rsUpper, '$'].join('|') + ')',
+    rsUpperMisc + '+' + rsOptUpperContr + '(?=' + [rsBreak, rsUpper + rsLowerMisc, '$'].join('|') + ')',
+    rsUpper + '?' + rsLowerMisc + '+' + rsOptLowerContr,
+    rsUpper + '+' + rsOptUpperContr,
+    rsDigits,
+    rsEmoji
+  ].join('|'), 'g');
+
+  /** Used to detect strings with [zero-width joiners or code points from the astral planes](http://eev.ee/blog/2015/09/12/dark-corners-of-unicode/). */
+  var reHasComplexSymbol = RegExp('[' + rsZWJ + rsAstralRange  + rsComboMarksRange + rsComboSymbolsRange + rsVarRange + ']');
+
+  /** Used to detect strings that need a more robust regexp to match words. */
+  var reHasComplexWord = /[a-z][A-Z]|[A-Z]{2,}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/;
+
+  /** Used to assign default `context` object properties. */
+  var contextProps = [
+    'Array', 'Buffer', 'DataView', 'Date', 'Error', 'Float32Array', 'Float64Array',
+    'Function', 'Int8Array', 'Int16Array', 'Int32Array', 'Map', 'Math', 'Object',
+    'Promise', 'Reflect', 'RegExp', 'Set', 'String', 'Symbol', 'TypeError',
+    'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array', 'WeakMap',
+    '_', 'clearTimeout', 'isFinite', 'parseInt', 'setTimeout'
+  ];
+
+  /** Used to make template sourceURLs easier to identify. */
+  var templateCounter = -1;
+
+  /** Used to identify `toStringTag` values of typed arrays. */
+  var typedArrayTags = {};
+  typedArrayTags[float32Tag] = typedArrayTags[float64Tag] =
+  typedArrayTags[int8Tag] = typedArrayTags[int16Tag] =
+  typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] =
+  typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] =
+  typedArrayTags[uint32Tag] = true;
+  typedArrayTags[argsTag] = typedArrayTags[arrayTag] =
+  typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] =
+  typedArrayTags[dataViewTag] = typedArrayTags[dateTag] =
+  typedArrayTags[errorTag] = typedArrayTags[funcTag] =
+  typedArrayTags[mapTag] = typedArrayTags[numberTag] =
+  typedArrayTags[objectTag] = typedArrayTags[regexpTag] =
+  typedArrayTags[setTag] = typedArrayTags[stringTag] =
+  typedArrayTags[weakMapTag] = false;
+
+  /** Used to identify `toStringTag` values supported by `_.clone`. */
+  var cloneableTags = {};
+  cloneableTags[argsTag] = cloneableTags[arrayTag] =
+  cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] =
+  cloneableTags[boolTag] = cloneableTags[dateTag] =
+  cloneableTags[float32Tag] = cloneableTags[float64Tag] =
+  cloneableTags[int8Tag] = cloneableTags[int16Tag] =
+  cloneableTags[int32Tag] = cloneableTags[mapTag] =
+  cloneableTags[numberTag] = cloneableTags[objectTag] =
+  cloneableTags[regexpTag] = cloneableTags[setTag] =
+  cloneableTags[stringTag] = cloneableTags[symbolTag] =
+  cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] =
+  cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true;
+  cloneableTags[errorTag] = cloneableTags[funcTag] =
+  cloneableTags[weakMapTag] = false;
+
+  /** Used to map latin-1 supplementary letters to basic latin letters. */
+  var deburredLetters = {
+    '\xc0': 'A',  '\xc1': 'A', '\xc2': 'A', '\xc3': 'A', '\xc4': 'A', '\xc5': 'A',
+    '\xe0': 'a',  '\xe1': 'a', '\xe2': 'a', '\xe3': 'a', '\xe4': 'a', '\xe5': 'a',
+    '\xc7': 'C',  '\xe7': 'c',
+    '\xd0': 'D',  '\xf0': 'd',
+    '\xc8': 'E',  '\xc9': 'E', '\xca': 'E', '\xcb': 'E',
+    '\xe8': 'e',  '\xe9': 'e', '\xea': 'e', '\xeb': 'e',
+    '\xcC': 'I',  '\xcd': 'I', '\xce': 'I', '\xcf': 'I',
+    '\xeC': 'i',  '\xed': 'i', '\xee': 'i', '\xef': 'i',
+    '\xd1': 'N',  '\xf1': 'n',
+    '\xd2': 'O',  '\xd3': 'O', '\xd4': 'O', '\xd5': 'O', '\xd6': 'O', '\xd8': 'O',
+    '\xf2': 'o',  '\xf3': 'o', '\xf4': 'o', '\xf5': 'o', '\xf6': 'o', '\xf8': 'o',
+    '\xd9': 'U',  '\xda': 'U', '\xdb': 'U', '\xdc': 'U',
+    '\xf9': 'u',  '\xfa': 'u', '\xfb': 'u', '\xfc': 'u',
+    '\xdd': 'Y',  '\xfd': 'y', '\xff': 'y',
+    '\xc6': 'Ae', '\xe6': 'ae',
+    '\xde': 'Th', '\xfe': 'th',
+    '\xdf': 'ss'
+  };
+
+  /** Used to map characters to HTML entities. */
+  var htmlEscapes = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#39;',
+    '`': '&#96;'
+  };
+
+  /** Used to map HTML entities to characters. */
+  var htmlUnescapes = {
+    '&amp;': '&',
+    '&lt;': '<',
+    '&gt;': '>',
+    '&quot;': '"',
+    '&#39;': "'",
+    '&#96;': '`'
+  };
+
+  /** Used to determine if values are of the language type `Object`. */
+  var objectTypes = {
+    'function': true,
+    'object': true
+  };
+
+  /** Used to escape characters for inclusion in compiled string literals. */
+  var stringEscapes = {
+    '\\': '\\',
+    "'": "'",
+    '\n': 'n',
+    '\r': 'r',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  /** Built-in method references without a dependency on `root`. */
+  var freeParseFloat = parseFloat,
+      freeParseInt = parseInt;
+
+  /** Detect free variable `exports`. */
+  var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType)
+    ? exports
+    : undefined;
+
+  /** Detect free variable `module`. */
+  var freeModule = (objectTypes[typeof module] && module && !module.nodeType)
+    ? module
+    : undefined;
+
+  /** Detect the popular CommonJS extension `module.exports`. */
+  var moduleExports = (freeModule && freeModule.exports === freeExports)
+    ? freeExports
+    : undefined;
+
+  /** Detect free variable `global` from Node.js. */
+  var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global);
+
+  /** Detect free variable `self`. */
+  var freeSelf = checkGlobal(objectTypes[typeof self] && self);
+
+  /** Detect free variable `window`. */
+  var freeWindow = checkGlobal(objectTypes[typeof window] && window);
+
+  /** Detect `this` as the global object. */
+  var thisGlobal = checkGlobal(objectTypes[typeof this] && this);
+
+  /**
+   * Used as a reference to the global object.
+   *
+   * The `this` value is used if it's the global object to avoid Greasemonkey's
+   * restricted `window` object, otherwise the `window` object is used.
+   */
+  var root = freeGlobal ||
+    ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) ||
+      freeSelf || thisGlobal || Function('return this')();
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Adds the key-value `pair` to `map`.
+   *
+   * @private
+   * @param {Object} map The map to modify.
+   * @param {Array} pair The key-value pair to add.
+   * @returns {Object} Returns `map`.
+   */
+  function addMapEntry(map, pair) {
+    // Don't return `Map#set` because it doesn't return the map instance in IE 11.
+    map.set(pair[0], pair[1]);
+    return map;
+  }
+
+  /**
+   * Adds `value` to `set`.
+   *
+   * @private
+   * @param {Object} set The set to modify.
+   * @param {*} value The value to add.
+   * @returns {Object} Returns `set`.
+   */
+  function addSetEntry(set, value) {
+    set.add(value);
+    return set;
+  }
+
+  /**
+   * A faster alternative to `Function#apply`, this function invokes `func`
+   * with the `this` binding of `thisArg` and the arguments of `args`.
+   *
+   * @private
+   * @param {Function} func The function to invoke.
+   * @param {*} thisArg The `this` binding of `func`.
+   * @param {Array} args The arguments to invoke `func` with.
+   * @returns {*} Returns the result of `func`.
+   */
+  function apply(func, thisArg, args) {
+    var length = args.length;
+    switch (length) {
+      case 0: return func.call(thisArg);
+      case 1: return func.call(thisArg, args[0]);
+      case 2: return func.call(thisArg, args[0], args[1]);
+      case 3: return func.call(thisArg, args[0], args[1], args[2]);
+    }
+    return func.apply(thisArg, args);
+  }
+
+  /**
+   * A specialized version of `baseAggregator` for arrays.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} setter The function to set `accumulator` values.
+   * @param {Function} iteratee The iteratee to transform keys.
+   * @param {Object} accumulator The initial aggregated object.
+   * @returns {Function} Returns `accumulator`.
+   */
+  function arrayAggregator(array, setter, iteratee, accumulator) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var value = array[index];
+      setter(accumulator, value, iteratee(value), array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * Creates a new array concatenating `array` with `other`.
+   *
+   * @private
+   * @param {Array} array The first array to concatenate.
+   * @param {Array} other The second array to concatenate.
+   * @returns {Array} Returns the new concatenated array.
+   */
+  function arrayConcat(array, other) {
+    var index = -1,
+        length = array.length,
+        othIndex = -1,
+        othLength = other.length,
+        result = Array(length + othLength);
+
+    while (++index < length) {
+      result[index] = array[index];
+    }
+    while (++othIndex < othLength) {
+      result[index++] = other[othIndex];
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.forEach` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEach(array, iteratee) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (iteratee(array[index], index, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.forEachRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayEachRight(array, iteratee) {
+    var length = array.length;
+
+    while (length--) {
+      if (iteratee(array[length], length, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.every` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if all elements pass the predicate check,
+   *  else `false`.
+   */
+  function arrayEvery(array, predicate) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (!predicate(array[index], index, array)) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  /**
+   * A specialized version of `_.filter` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {Array} Returns the new filtered array.
+   */
+  function arrayFilter(array, predicate) {
+    var index = -1,
+        length = array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (predicate(value, index, array)) {
+        result[resIndex++] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * A specialized version of `_.includes` for arrays without support for
+   * specifying an index to search from.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} target The value to search for.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludes(array, value) {
+    return !!array.length && baseIndexOf(array, value, 0) > -1;
+  }
+
+  /**
+   * This function is like `arrayIncludes` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} target The value to search for.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {boolean} Returns `true` if `target` is found, else `false`.
+   */
+  function arrayIncludesWith(array, value, comparator) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (comparator(value, array[index])) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * A specialized version of `_.map` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the new mapped array.
+   */
+  function arrayMap(array, iteratee) {
+    var index = -1,
+        length = array.length,
+        result = Array(length);
+
+    while (++index < length) {
+      result[index] = iteratee(array[index], index, array);
+    }
+    return result;
+  }
+
+  /**
+   * Appends the elements of `values` to `array`.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {Array} values The values to append.
+   * @returns {Array} Returns `array`.
+   */
+  function arrayPush(array, values) {
+    var index = -1,
+        length = values.length,
+        offset = array.length;
+
+    while (++index < length) {
+      array[offset + index] = values[index];
+    }
+    return array;
+  }
+
+  /**
+   * A specialized version of `_.reduce` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the first element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduce(array, iteratee, accumulator, initAccum) {
+    var index = -1,
+        length = array.length;
+
+    if (initAccum && length) {
+      accumulator = array[++index];
+    }
+    while (++index < length) {
+      accumulator = iteratee(accumulator, array[index], index, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.reduceRight` for arrays without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} [accumulator] The initial value.
+   * @param {boolean} [initAccum] Specify using the last element of `array` as
+   *  the initial value.
+   * @returns {*} Returns the accumulated value.
+   */
+  function arrayReduceRight(array, iteratee, accumulator, initAccum) {
+    var length = array.length;
+    if (initAccum && length) {
+      accumulator = array[--length];
+    }
+    while (length--) {
+      accumulator = iteratee(accumulator, array[length], length, array);
+    }
+    return accumulator;
+  }
+
+  /**
+   * A specialized version of `_.some` for arrays without support for iteratee
+   * shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} predicate The function invoked per iteration.
+   * @returns {boolean} Returns `true` if any element passes the predicate check,
+   *  else `false`.
+   */
+  function arraySome(array, predicate) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      if (predicate(array[index], index, array)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
+  /**
+   * The base implementation of methods like `_.find` and `_.findKey`, without
+   * support for iteratee shorthands, which iterates over `collection` using
+   * `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to search.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @param {boolean} [retKey] Specify returning the key of the found element
+   *  instead of the element itself.
+   * @returns {*} Returns the found element or its key, else `undefined`.
+   */
+  function baseFind(collection, predicate, eachFunc, retKey) {
+    var result;
+    eachFunc(collection, function(value, key, collection) {
+      if (predicate(value, key, collection)) {
+        result = retKey ? key : value;
+        return false;
+      }
+    });
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.findIndex` and `_.findLastIndex` without
+   * support for iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {Function} predicate The function invoked per iteration.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseFindIndex(array, predicate, fromRight) {
+    var length = array.length,
+        index = fromRight ? length : -1;
+
+    while ((fromRight ? index-- : ++index < length)) {
+      if (predicate(array[index], index, array)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.indexOf` without `fromIndex` bounds checks.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOf(array, value, fromIndex) {
+    if (value !== value) {
+      return indexOfNaN(array, fromIndex);
+    }
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (array[index] === value) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * This function is like `baseIndexOf` except that it accepts a comparator.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {*} value The value to search for.
+   * @param {number} fromIndex The index to search from.
+   * @param {Function} comparator The comparator invoked per element.
+   * @returns {number} Returns the index of the matched value, else `-1`.
+   */
+  function baseIndexOfWith(array, value, fromIndex, comparator) {
+    var index = fromIndex - 1,
+        length = array.length;
+
+    while (++index < length) {
+      if (comparator(array[index], value)) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * The base implementation of `_.mean` and `_.meanBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the mean.
+   */
+  function baseMean(array, iteratee) {
+    var length = array ? array.length : 0;
+    return length ? (baseSum(array, iteratee) / length) : NAN;
+  }
+
+  /**
+   * The base implementation of `_.reduce` and `_.reduceRight`, without support
+   * for iteratee shorthands, which iterates over `collection` using `eachFunc`.
+   *
+   * @private
+   * @param {Array|Object} collection The collection to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @param {*} accumulator The initial value.
+   * @param {boolean} initAccum Specify using the first or last element of
+   *  `collection` as the initial value.
+   * @param {Function} eachFunc The function to iterate over `collection`.
+   * @returns {*} Returns the accumulated value.
+   */
+  function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) {
+    eachFunc(collection, function(value, index, collection) {
+      accumulator = initAccum
+        ? (initAccum = false, value)
+        : iteratee(accumulator, value, index, collection);
+    });
+    return accumulator;
+  }
+
+  /**
+   * The base implementation of `_.sortBy` which uses `comparer` to define the
+   * sort order of `array` and replaces criteria objects with their corresponding
+   * values.
+   *
+   * @private
+   * @param {Array} array The array to sort.
+   * @param {Function} comparer The function to define sort order.
+   * @returns {Array} Returns `array`.
+   */
+  function baseSortBy(array, comparer) {
+    var length = array.length;
+
+    array.sort(comparer);
+    while (length--) {
+      array[length] = array[length].value;
+    }
+    return array;
+  }
+
+  /**
+   * The base implementation of `_.sum` and `_.sumBy` without support for
+   * iteratee shorthands.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {number} Returns the sum.
+   */
+  function baseSum(array, iteratee) {
+    var result,
+        index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      var current = iteratee(array[index]);
+      if (current !== undefined) {
+        result = result === undefined ? current : (result + current);
+      }
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.times` without support for iteratee shorthands
+   * or max array length checks.
+   *
+   * @private
+   * @param {number} n The number of times to invoke `iteratee`.
+   * @param {Function} iteratee The function invoked per iteration.
+   * @returns {Array} Returns the array of results.
+   */
+  function baseTimes(n, iteratee) {
+    var index = -1,
+        result = Array(n);
+
+    while (++index < n) {
+      result[index] = iteratee(index);
+    }
+    return result;
+  }
+
+  /**
+   * The base implementation of `_.toPairs` and `_.toPairsIn` which creates an array
+   * of key-value pairs for `object` corresponding to the property names of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the new array of key-value pairs.
+   */
+  function baseToPairs(object, props) {
+    return arrayMap(props, function(key) {
+      return [key, object[key]];
+    });
+  }
+
+  /**
+   * The base implementation of `_.unary` without support for storing wrapper metadata.
+   *
+   * @private
+   * @param {Function} func The function to cap arguments for.
+   * @returns {Function} Returns the new function.
+   */
+  function baseUnary(func) {
+    return function(value) {
+      return func(value);
+    };
+  }
+
+  /**
+   * The base implementation of `_.values` and `_.valuesIn` which creates an
+   * array of `object` property values corresponding to the property names
+   * of `props`.
+   *
+   * @private
+   * @param {Object} object The object to query.
+   * @param {Array} props The property names to get values for.
+   * @returns {Object} Returns the array of property values.
+   */
+  function baseValues(object, props) {
+    return arrayMap(props, function(key) {
+      return object[key];
+    });
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimStart` to get the index of the first string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the first unmatched string symbol.
+   */
+  function charsStartIndex(strSymbols, chrSymbols) {
+    var index = -1,
+        length = strSymbols.length;
+
+    while (++index < length && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Used by `_.trim` and `_.trimEnd` to get the index of the last string symbol
+   * that is not found in the character symbols.
+   *
+   * @private
+   * @param {Array} strSymbols The string symbols to inspect.
+   * @param {Array} chrSymbols The character symbols to find.
+   * @returns {number} Returns the index of the last unmatched string symbol.
+   */
+  function charsEndIndex(strSymbols, chrSymbols) {
+    var index = strSymbols.length;
+
+    while (index-- && baseIndexOf(chrSymbols, strSymbols[index], 0) > -1) {}
+    return index;
+  }
+
+  /**
+   * Checks if `value` is a global object.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {null|Object} Returns `value` if it's a global object, else `null`.
+   */
+  function checkGlobal(value) {
+    return (value && value.Object === Object) ? value : null;
+  }
+
+  /**
+   * Gets the number of `placeholder` occurrences in `array`.
+   *
+   * @private
+   * @param {Array} array The array to inspect.
+   * @param {*} placeholder The placeholder to search for.
+   * @returns {number} Returns the placeholder count.
+   */
+  function countHolders(array, placeholder) {
+    var length = array.length,
+        result = 0;
+
+    while (length--) {
+      if (array[length] === placeholder) {
+        result++;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Used by `_.deburr` to convert latin-1 supplementary letters to basic latin letters.
+   *
+   * @private
+   * @param {string} letter The matched letter to deburr.
+   * @returns {string} Returns the deburred letter.
+   */
+  function deburrLetter(letter) {
+    return deburredLetters[letter];
+  }
+
+  /**
+   * Used by `_.escape` to convert characters to HTML entities.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeHtmlChar(chr) {
+    return htmlEscapes[chr];
+  }
+
+  /**
+   * Used by `_.template` to escape characters for inclusion in compiled string literals.
+   *
+   * @private
+   * @param {string} chr The matched character to escape.
+   * @returns {string} Returns the escaped character.
+   */
+  function escapeStringChar(chr) {
+    return '\\' + stringEscapes[chr];
+  }
+
+  /**
+   * Gets the index at which the first occurrence of `NaN` is found in `array`.
+   *
+   * @private
+   * @param {Array} array The array to search.
+   * @param {number} fromIndex The index to search from.
+   * @param {boolean} [fromRight] Specify iterating from right to left.
+   * @returns {number} Returns the index of the matched `NaN`, else `-1`.
+   */
+  function indexOfNaN(array, fromIndex, fromRight) {
+    var length = array.length,
+        index = fromIndex + (fromRight ? 0 : -1);
+
+    while ((fromRight ? index-- : ++index < length)) {
+      var other = array[index];
+      if (other !== other) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Checks if `value` is a host object in IE < 9.
+   *
+   * @private
+   * @param {*} value The value to check.
+   * @returns {boolean} Returns `true` if `value` is a host object, else `false`.
+   */
+  function isHostObject(value) {
+    // Many host objects are `Object` objects that can coerce to strings
+    // despite having improperly defined `toString` methods.
+    var result = false;
+    if (value != null && typeof value.toString != 'function') {
+      try {
+        result = !!(value + '');
+      } catch (e) {}
+    }
+    return result;
+  }
+
+  /**
+   * Converts `iterator` to an array.
+   *
+   * @private
+   * @param {Object} iterator The iterator to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function iteratorToArray(iterator) {
+    var data,
+        result = [];
+
+    while (!(data = iterator.next()).done) {
+      result.push(data.value);
+    }
+    return result;
+  }
+
+  /**
+   * Converts `map` to an array.
+   *
+   * @private
+   * @param {Object} map The map to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function mapToArray(map) {
+    var index = -1,
+        result = Array(map.size);
+
+    map.forEach(function(value, key) {
+      result[++index] = [key, value];
+    });
+    return result;
+  }
+
+  /**
+   * Replaces all `placeholder` elements in `array` with an internal placeholder
+   * and returns an array of their indexes.
+   *
+   * @private
+   * @param {Array} array The array to modify.
+   * @param {*} placeholder The placeholder to replace.
+   * @returns {Array} Returns the new array of placeholder indexes.
+   */
+  function replaceHolders(array, placeholder) {
+    var index = -1,
+        length = array.length,
+        resIndex = 0,
+        result = [];
+
+    while (++index < length) {
+      var value = array[index];
+      if (value === placeholder || value === PLACEHOLDER) {
+        array[index] = PLACEHOLDER;
+        result[resIndex++] = index;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Converts `set` to an array.
+   *
+   * @private
+   * @param {Object} set The set to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function setToArray(set) {
+    var index = -1,
+        result = Array(set.size);
+
+    set.forEach(function(value) {
+      result[++index] = value;
+    });
+    return result;
+  }
+
+  /**
+   * Gets the number of symbols in `string`.
+   *
+   * @private
+   * @param {string} string The string to inspect.
+   * @returns {number} Returns the string size.
+   */
+  function stringSize(string) {
+    if (!(string && reHasComplexSymbol.test(string))) {
+      return string.length;
+    }
+    var result = reComplexSymbol.lastIndex = 0;
+    while (reComplexSymbol.test(string)) {
+      result++;
+    }
+    return result;
+  }
+
+  /**
+   * Converts `string` to an array.
+   *
+   * @private
+   * @param {string} string The string to convert.
+   * @returns {Array} Returns the converted array.
+   */
+  function stringToArray(string) {
+    return string.match(reComplexSymbol);
+  }
+
+  /**
+   * Used by `_.unescape` to convert HTML entities to characters.
+   *
+   * @private
+   * @param {string} chr The matched character to unescape.
+   * @returns {string} Returns the unescaped character.
+   */
+  function unescapeHtmlChar(chr) {
+    return htmlUnescapes[chr];
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Create a new pristine `lodash` function using the `context` object.
+   *
+   * @static
+   * @memberOf _
+   * @since 1.1.0
+   * @category Util
+   * @param {Object} [context=root] The context object.
+   * @returns {Function} Returns a new `lodash` function.
+   * @example
+   *
+   * _.mixin({ 'foo': _.constant('foo') });
+   *
+   * var lodash = _.runInContext();
+   * lodash.mixin({ 'bar': lodash.constant('bar') });
+   *
+   * _.isFunction(_.foo);
+   * // => true
+   * _.isFunction(_.bar);
+   * // => false
+   *
+   * lodash.isFunction(lodash.foo);
+   * // => false
+   * lodash.isFunction(lodash.bar);
+   * // => true
+   *
+   * // Use `context` to mock `Date#getTime` use in `_.now`.
+   * var mock = _.runInContext({
+   *   'Date': function() {
+   *     return { 'getTime': getTimeMock };
+   *   }
+   * });
+   *
+   * // Create a suped-up `defer` in Node.js.
+   * var defer = _.runInContext({ 'setTimeout': setImmediate }).defer;
+   */
+  function runInContext(context) {
+    context = context ? _.defaults({}, context, _.pick(root, contextProps)) : root;
+
+    /** Built-in constructor references. */
+    var Date = context.Date,
+        Error = context.Error,
+        Math = context.Math,
+        RegExp = context.RegExp,
+        TypeError = context.TypeError;
+
+    /** Used for built-in method references. */
+    var arrayProto = context.Array.prototype,
+        objectProto = context.Object.prototype,
+        stringProto = context.String.prototype;
+
+    /** Used to resolve the decompiled source of functions. */
+    var funcToString = context.Function.prototype.toString;
+
+    /** Used to check objects for own properties. */
+    var hasOwnProperty = objectProto.hasOwnProperty;
+
+    /** Used to generate unique IDs. */
+    var idCounter = 0;
+
+    /** Used to infer the `Object` constructor. */
+    var objectCtorString = funcToString.call(Object);
+
+    /**
+     * Used to resolve the
+     * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring)
+     * of values.
+     */
+    var objectToString = objectProto.toString;
+
+    /** Used to restore the original `_` reference in `_.noConflict`. */
+    var oldDash = root._;
+
+    /** Used to detect if a method is native. */
+    var reIsNative = RegExp('^' +
+      funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&')
+      .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$'
+    );
+
+    /** Built-in value references. */
+    var Buffer = moduleExports ? context.Buffer : undefined,
+        Reflect = context.Reflect,
+        Symbol = context.Symbol,
+        Uint8Array = context.Uint8Array,
+        clearTimeout = context.clearTimeout,
+        enumerate = Reflect ? Reflect.enumerate : undefined,
+        getOwnPropertySymbols = Object.getOwnPropertySymbols,
+        iteratorSymbol = typeof (iteratorSymbol = Symbol && Symbol.iterator) == 'symbol' ? iteratorSymbol : undefined,
+        objectCreate = Object.create,
+        propertyIsEnumerable = objectProto.propertyIsEnumerable,
+        setTimeout = context.setTimeout,
+        splice = arrayProto.splice;
+
+    /* Built-in method references for those with the same name as other `lodash` methods. */
+    var nativeCeil = Math.ceil,
+        nativeFloor = Math.floor,
+        nativeGetPrototype = Object.getPrototypeOf,
+        nativeIsFinite = context.isFinite,
+        nativeJoin = arrayProto.join,
+        nativeKeys = Object.keys,
+        nativeMax = Math.max,
+        nativeMin = Math.min,
+        nativeParseInt = context.parseInt,
+        nativeRandom = Math.random,
+        nativeReplace = stringProto.replace,
+        nativeReverse = arrayProto.reverse,
+        nativeSplit = stringProto.split;
+
+    /* Built-in method references that are verified to be native. */
+    var DataView = getNative(context, 'DataView'),
+        Map = getNative(context, 'Map'),
+        Promise = getNative(context, 'Promise'),
+        Set = getNative(context, 'Set'),
+        WeakMap = getNative(context, 'WeakMap'),
+        nativeCreate = getNative(Object, 'create');
+
+    /** Used to store function metadata. */
+    var metaMap = WeakMap && new WeakMap;
+
+    /** Detect if properties shadowing those on `Object.prototype` are non-enumerable. */
+    var nonEnumShadows = !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf');
+
+    /** Used to lookup unminified function names. */
+    var realNames = {};
+
+    /** Used to detect maps, sets, and weakmaps. */
+    var dataViewCtorString = toSource(DataView),
+        mapCtorString = toSource(Map),
+        promiseCtorString = toSource(Promise),
+        setCtorString = toSource(Set),
+        weakMapCtorString = toSource(WeakMap);
+
+    /** Used to convert symbols to primitives and strings. */
+    var symbolProto = Symbol ? Symbol.prototype : undefined,
+        symbolValueOf = symbolProto ? symbolProto.valueOf : undefined,
+        symbolToString = symbolProto ? symbolProto.toString : undefined;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` object which wraps `value` to enable implicit method
+     * chain sequences. Methods that operate on and return arrays, collections,
+     * and functions can be chained together. Methods that retrieve a single value
+     * or may return a primitive value will automatically end the chain sequence
+     * and return the unwrapped value. Otherwise, the value must be unwrapped
+     * with `_#value`.
+     *
+     * Explicit chain sequences, which must be unwrapped with `_#value`, may be
+     * enabled using `_.chain`.
+     *
+     * The execution of chained methods is lazy, that is, it's deferred until
+     * `_#value` is implicitly or explicitly called.
+     *
+     * Lazy evaluation allows several methods to support shortcut fusion.
+     * Shortcut fusion is an optimization to merge iteratee calls; this avoids
+     * the creation of intermediate arrays and can greatly reduce the number of
+     * iteratee executions. Sections of a chain sequence qualify for shortcut
+     * fusion if the section is applied to an array of at least `200` elements
+     * and any iteratees accept only one argument. The heuristic for whether a
+     * section qualifies for shortcut fusion is subject to change.
+     *
+     * Chaining is supported in custom builds as long as the `_#value` method is
+     * directly or indirectly included in the build.
+     *
+     * In addition to lodash methods, wrappers have `Array` and `String` methods.
+     *
+     * The wrapper `Array` methods are:
+     * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift`
+     *
+     * The wrapper `String` methods are:
+     * `replace` and `split`
+     *
+     * The wrapper methods that support shortcut fusion are:
+     * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`,
+     * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`,
+     * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray`
+     *
+     * The chainable wrapper methods are:
+     * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`,
+     * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`,
+     * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`,
+     * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`,
+     * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`,
+     * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`,
+     * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`,
+     * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`,
+     * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`,
+     * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`,
+     * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`,
+     * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`,
+     * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`,
+     * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`,
+     * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`,
+     * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`,
+     * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`,
+     * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`,
+     * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`,
+     * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`,
+     * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`,
+     * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`,
+     * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`,
+     * `zipObject`, `zipObjectDeep`, and `zipWith`
+     *
+     * The wrapper methods that are **not** chainable by default are:
+     * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`,
+     * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `deburr`, `divide`, `each`,
+     * `eachRight`, `endsWith`, `eq`, `escape`, `escapeRegExp`, `every`, `find`,
+     * `findIndex`, `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `first`,
+     * `floor`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`,
+     * `forOwnRight`, `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`,
+     * `includes`, `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`,
+     * `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, `isBuffer`,
+     * `isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, `isError`,
+     * `isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, `isMatch`,
+     * `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`,
+     * `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`,
+     * `isSet`, `isString`, `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`,
+     * `join`, `kebabCase`, `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`,
+     * `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, `min`, `minBy`, `multiply`,
+     * `noConflict`, `noop`, `now`, `nth`, `pad`, `padEnd`, `padStart`, `parseInt`,
+     * `pop`, `random`, `reduce`, `reduceRight`, `repeat`, `result`, `round`,
+     * `runInContext`, `sample`, `shift`, `size`, `snakeCase`, `some`, `sortedIndex`,
+     * `sortedIndexBy`, `sortedLastIndex`, `sortedLastIndexBy`, `startCase`,
+     * `startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toInteger`,
+     * `toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, `toString`,
+     * `toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`,
+     * `uniqueId`, `upperCase`, `upperFirst`, `value`, and `words`
+     *
+     * @name _
+     * @constructor
+     * @category Seq
+     * @param {*} value The value to wrap in a `lodash` instance.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2, 3]);
+     *
+     * // Returns an unwrapped value.
+     * wrapped.reduce(_.add);
+     * // => 6
+     *
+     * // Returns a wrapped value.
+     * var squares = wrapped.map(square);
+     *
+     * _.isArray(squares);
+     * // => false
+     *
+     * _.isArray(squares.value());
+     * // => true
+     */
+    function lodash(value) {
+      if (isObjectLike(value) && !isArray(value) && !(value instanceof LazyWrapper)) {
+        if (value instanceof LodashWrapper) {
+          return value;
+        }
+        if (hasOwnProperty.call(value, '__wrapped__')) {
+          return wrapperClone(value);
+        }
+      }
+      return new LodashWrapper(value);
+    }
+
+    /**
+     * The function whose prototype chain sequence wrappers inherit from.
+     *
+     * @private
+     */
+    function baseLodash() {
+      // No operation performed.
+    }
+
+    /**
+     * The base constructor for creating `lodash` wrapper objects.
+     *
+     * @private
+     * @param {*} value The value to wrap.
+     * @param {boolean} [chainAll] Enable explicit method chain sequences.
+     */
+    function LodashWrapper(value, chainAll) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__chain__ = !!chainAll;
+      this.__index__ = 0;
+      this.__values__ = undefined;
+    }
+
+    /**
+     * By default, the template delimiters used by lodash are like 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': reEscape,
+
+      /**
+       * Used to detect code to be evaluated.
+       *
+       * @memberOf _.templateSettings
+       * @type {RegExp}
+       */
+      'evaluate': reEvaluate,
+
+      /**
+       * 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
+      }
+    };
+
+    // Ensure wrappers are instances of `baseLodash`.
+    lodash.prototype = baseLodash.prototype;
+    lodash.prototype.constructor = lodash;
+
+    LodashWrapper.prototype = baseCreate(baseLodash.prototype);
+    LodashWrapper.prototype.constructor = LodashWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a lazy wrapper object which wraps `value` to enable lazy evaluation.
+     *
+     * @private
+     * @constructor
+     * @param {*} value The value to wrap.
+     */
+    function LazyWrapper(value) {
+      this.__wrapped__ = value;
+      this.__actions__ = [];
+      this.__dir__ = 1;
+      this.__filtered__ = false;
+      this.__iteratees__ = [];
+      this.__takeCount__ = MAX_ARRAY_LENGTH;
+      this.__views__ = [];
+    }
+
+    /**
+     * Creates a clone of the lazy wrapper object.
+     *
+     * @private
+     * @name clone
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the cloned `LazyWrapper` object.
+     */
+    function lazyClone() {
+      var result = new LazyWrapper(this.__wrapped__);
+      result.__actions__ = copyArray(this.__actions__);
+      result.__dir__ = this.__dir__;
+      result.__filtered__ = this.__filtered__;
+      result.__iteratees__ = copyArray(this.__iteratees__);
+      result.__takeCount__ = this.__takeCount__;
+      result.__views__ = copyArray(this.__views__);
+      return result;
+    }
+
+    /**
+     * Reverses the direction of lazy iteration.
+     *
+     * @private
+     * @name reverse
+     * @memberOf LazyWrapper
+     * @returns {Object} Returns the new reversed `LazyWrapper` object.
+     */
+    function lazyReverse() {
+      if (this.__filtered__) {
+        var result = new LazyWrapper(this);
+        result.__dir__ = -1;
+        result.__filtered__ = true;
+      } else {
+        result = this.clone();
+        result.__dir__ *= -1;
+      }
+      return result;
+    }
+
+    /**
+     * Extracts the unwrapped value from its lazy wrapper.
+     *
+     * @private
+     * @name value
+     * @memberOf LazyWrapper
+     * @returns {*} Returns the unwrapped value.
+     */
+    function lazyValue() {
+      var array = this.__wrapped__.value(),
+          dir = this.__dir__,
+          isArr = isArray(array),
+          isRight = dir < 0,
+          arrLength = isArr ? array.length : 0,
+          view = getView(0, arrLength, this.__views__),
+          start = view.start,
+          end = view.end,
+          length = end - start,
+          index = isRight ? end : (start - 1),
+          iteratees = this.__iteratees__,
+          iterLength = iteratees.length,
+          resIndex = 0,
+          takeCount = nativeMin(length, this.__takeCount__);
+
+      if (!isArr || arrLength < LARGE_ARRAY_SIZE ||
+          (arrLength == length && takeCount == length)) {
+        return baseWrapperValue(array, this.__actions__);
+      }
+      var result = [];
+
+      outer:
+      while (length-- && resIndex < takeCount) {
+        index += dir;
+
+        var iterIndex = -1,
+            value = array[index];
+
+        while (++iterIndex < iterLength) {
+          var data = iteratees[iterIndex],
+              iteratee = data.iteratee,
+              type = data.type,
+              computed = iteratee(value);
+
+          if (type == LAZY_MAP_FLAG) {
+            value = computed;
+          } else if (!computed) {
+            if (type == LAZY_FILTER_FLAG) {
+              continue outer;
+            } else {
+              break outer;
+            }
+          }
+        }
+        result[resIndex++] = value;
+      }
+      return result;
+    }
+
+    // Ensure `LazyWrapper` is an instance of `baseLodash`.
+    LazyWrapper.prototype = baseCreate(baseLodash.prototype);
+    LazyWrapper.prototype.constructor = LazyWrapper;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a hash object.
+     *
+     * @private
+     * @constructor
+     * @returns {Object} Returns the new hash object.
+     */
+    function Hash() {}
+
+    /**
+     * Removes `key` and its value from the hash.
+     *
+     * @private
+     * @param {Object} hash The hash to modify.
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function hashDelete(hash, key) {
+      return hashHas(hash, key) && delete hash[key];
+    }
+
+    /**
+     * Gets the hash value for `key`.
+     *
+     * @private
+     * @param {Object} hash The hash to query.
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function hashGet(hash, key) {
+      if (nativeCreate) {
+        var result = hash[key];
+        return result === HASH_UNDEFINED ? undefined : result;
+      }
+      return hasOwnProperty.call(hash, key) ? hash[key] : undefined;
+    }
+
+    /**
+     * Checks if a hash value for `key` exists.
+     *
+     * @private
+     * @param {Object} hash The hash to query.
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function hashHas(hash, key) {
+      return nativeCreate ? hash[key] !== undefined : hasOwnProperty.call(hash, key);
+    }
+
+    /**
+     * Sets the hash `key` to `value`.
+     *
+     * @private
+     * @param {Object} hash The hash to modify.
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     */
+    function hashSet(hash, key, value) {
+      hash[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value;
+    }
+
+    // Avoid inheriting from `Object.prototype` when possible.
+    Hash.prototype = nativeCreate ? nativeCreate(null) : objectProto;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a map cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function MapCache(values) {
+      var index = -1,
+          length = values ? values.length : 0;
+
+      this.clear();
+      while (++index < length) {
+        var entry = values[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the map.
+     *
+     * @private
+     * @name clear
+     * @memberOf MapCache
+     */
+    function mapClear() {
+      this.__data__ = {
+        'hash': new Hash,
+        'map': Map ? new Map : [],
+        'string': new Hash
+      };
+    }
+
+    /**
+     * Removes `key` and its value from the map.
+     *
+     * @private
+     * @name delete
+     * @memberOf MapCache
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function mapDelete(key) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        return hashDelete(typeof key == 'string' ? data.string : data.hash, key);
+      }
+      return Map ? data.map['delete'](key) : assocDelete(data.map, key);
+    }
+
+    /**
+     * Gets the map value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf MapCache
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function mapGet(key) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        return hashGet(typeof key == 'string' ? data.string : data.hash, key);
+      }
+      return Map ? data.map.get(key) : assocGet(data.map, key);
+    }
+
+    /**
+     * Checks if a map value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf MapCache
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function mapHas(key) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        return hashHas(typeof key == 'string' ? data.string : data.hash, key);
+      }
+      return Map ? data.map.has(key) : assocHas(data.map, key);
+    }
+
+    /**
+     * Sets the map `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf MapCache
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the map cache instance.
+     */
+    function mapSet(key, value) {
+      var data = this.__data__;
+      if (isKeyable(key)) {
+        hashSet(typeof key == 'string' ? data.string : data.hash, key, value);
+      } else if (Map) {
+        data.map.set(key, value);
+      } else {
+        assocSet(data.map, key, value);
+      }
+      return this;
+    }
+
+    // Add methods to `MapCache`.
+    MapCache.prototype.clear = mapClear;
+    MapCache.prototype['delete'] = mapDelete;
+    MapCache.prototype.get = mapGet;
+    MapCache.prototype.has = mapHas;
+    MapCache.prototype.set = mapSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     *
+     * Creates a set cache object to store unique values.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function SetCache(values) {
+      var index = -1,
+          length = values ? values.length : 0;
+
+      this.__data__ = new MapCache;
+      while (++index < length) {
+        this.push(values[index]);
+      }
+    }
+
+    /**
+     * Checks if `value` is in `cache`.
+     *
+     * @private
+     * @param {Object} cache The set cache to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns `true` if `value` is found, else `false`.
+     */
+    function cacheHas(cache, value) {
+      var map = cache.__data__;
+      if (isKeyable(value)) {
+        var data = map.__data__,
+            hash = typeof value == 'string' ? data.string : data.hash;
+
+        return hash[value] === HASH_UNDEFINED;
+      }
+      return map.has(value);
+    }
+
+    /**
+     * Adds `value` to the set cache.
+     *
+     * @private
+     * @name push
+     * @memberOf SetCache
+     * @param {*} value The value to cache.
+     */
+    function cachePush(value) {
+      var map = this.__data__;
+      if (isKeyable(value)) {
+        var data = map.__data__,
+            hash = typeof value == 'string' ? data.string : data.hash;
+
+        hash[value] = HASH_UNDEFINED;
+      }
+      else {
+        map.set(value, HASH_UNDEFINED);
+      }
+    }
+
+    // Add methods to `SetCache`.
+    SetCache.prototype.push = cachePush;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a stack cache object to store key-value pairs.
+     *
+     * @private
+     * @constructor
+     * @param {Array} [values] The values to cache.
+     */
+    function Stack(values) {
+      var index = -1,
+          length = values ? values.length : 0;
+
+      this.clear();
+      while (++index < length) {
+        var entry = values[index];
+        this.set(entry[0], entry[1]);
+      }
+    }
+
+    /**
+     * Removes all key-value entries from the stack.
+     *
+     * @private
+     * @name clear
+     * @memberOf Stack
+     */
+    function stackClear() {
+      this.__data__ = { 'array': [], 'map': null };
+    }
+
+    /**
+     * Removes `key` and its value from the stack.
+     *
+     * @private
+     * @name delete
+     * @memberOf Stack
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function stackDelete(key) {
+      var data = this.__data__,
+          array = data.array;
+
+      return array ? assocDelete(array, key) : data.map['delete'](key);
+    }
+
+    /**
+     * Gets the stack value for `key`.
+     *
+     * @private
+     * @name get
+     * @memberOf Stack
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function stackGet(key) {
+      var data = this.__data__,
+          array = data.array;
+
+      return array ? assocGet(array, key) : data.map.get(key);
+    }
+
+    /**
+     * Checks if a stack value for `key` exists.
+     *
+     * @private
+     * @name has
+     * @memberOf Stack
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function stackHas(key) {
+      var data = this.__data__,
+          array = data.array;
+
+      return array ? assocHas(array, key) : data.map.has(key);
+    }
+
+    /**
+     * Sets the stack `key` to `value`.
+     *
+     * @private
+     * @name set
+     * @memberOf Stack
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns the stack cache instance.
+     */
+    function stackSet(key, value) {
+      var data = this.__data__,
+          array = data.array;
+
+      if (array) {
+        if (array.length < (LARGE_ARRAY_SIZE - 1)) {
+          assocSet(array, key, value);
+        } else {
+          data.array = null;
+          data.map = new MapCache(array);
+        }
+      }
+      var map = data.map;
+      if (map) {
+        map.set(key, value);
+      }
+      return this;
+    }
+
+    // Add methods to `Stack`.
+    Stack.prototype.clear = stackClear;
+    Stack.prototype['delete'] = stackDelete;
+    Stack.prototype.get = stackGet;
+    Stack.prototype.has = stackHas;
+    Stack.prototype.set = stackSet;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Removes `key` and its value from the associative array.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {string} key The key of the value to remove.
+     * @returns {boolean} Returns `true` if the entry was removed, else `false`.
+     */
+    function assocDelete(array, key) {
+      var index = assocIndexOf(array, key);
+      if (index < 0) {
+        return false;
+      }
+      var lastIndex = array.length - 1;
+      if (index == lastIndex) {
+        array.pop();
+      } else {
+        splice.call(array, index, 1);
+      }
+      return true;
+    }
+
+    /**
+     * Gets the associative array value for `key`.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {string} key The key of the value to get.
+     * @returns {*} Returns the entry value.
+     */
+    function assocGet(array, key) {
+      var index = assocIndexOf(array, key);
+      return index < 0 ? undefined : array[index][1];
+    }
+
+    /**
+     * Checks if an associative array value for `key` exists.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {string} key The key of the entry to check.
+     * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`.
+     */
+    function assocHas(array, key) {
+      return assocIndexOf(array, key) > -1;
+    }
+
+    /**
+     * Gets the index at which the `key` is found in `array` of key-value pairs.
+     *
+     * @private
+     * @param {Array} array The array to search.
+     * @param {*} key The key to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     */
+    function assocIndexOf(array, key) {
+      var length = array.length;
+      while (length--) {
+        if (eq(array[length][0], key)) {
+          return length;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Sets the associative array `key` to `value`.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {string} key The key of the value to set.
+     * @param {*} value The value to set.
+     */
+    function assocSet(array, key, value) {
+      var index = assocIndexOf(array, key);
+      if (index < 0) {
+        array.push([key, value]);
+      } else {
+        array[index][1] = value;
+      }
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Used by `_.defaults` to customize its `_.assignIn` use.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to assign.
+     * @param {Object} object The parent object of `objValue`.
+     * @returns {*} Returns the value to assign.
+     */
+    function assignInDefaults(objValue, srcValue, key, object) {
+      if (objValue === undefined ||
+          (eq(objValue, objectProto[key]) && !hasOwnProperty.call(object, key))) {
+        return srcValue;
+      }
+      return objValue;
+    }
+
+    /**
+     * This function is like `assignValue` except that it doesn't assign
+     * `undefined` values.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignMergeValue(object, key, value) {
+      if ((value !== undefined && !eq(object[key], value)) ||
+          (typeof key == 'number' && value === undefined && !(key in object))) {
+        object[key] = value;
+      }
+    }
+
+    /**
+     * Assigns `value` to `key` of `object` if the existing value is not equivalent
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {string} key The key of the property to assign.
+     * @param {*} value The value to assign.
+     */
+    function assignValue(object, key, value) {
+      var objValue = object[key];
+      if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) ||
+          (value === undefined && !(key in object))) {
+        object[key] = value;
+      }
+    }
+
+    /**
+     * Aggregates elements of `collection` on `accumulator` with keys transformed
+     * by `iteratee` and values set by `setter`.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform keys.
+     * @param {Object} accumulator The initial aggregated object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseAggregator(collection, setter, iteratee, accumulator) {
+      baseEach(collection, function(value, key, collection) {
+        setter(accumulator, value, iteratee(value), collection);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.assign` without support for multiple sources
+     * or `customizer` functions.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @returns {Object} Returns `object`.
+     */
+    function baseAssign(object, source) {
+      return object && copyObject(source, keys(source), object);
+    }
+
+    /**
+     * The base implementation of `_.at` without support for individual paths.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {string[]} paths The property paths of elements to pick.
+     * @returns {Array} Returns the new array of picked elements.
+     */
+    function baseAt(object, paths) {
+      var index = -1,
+          isNil = object == null,
+          length = paths.length,
+          result = Array(length);
+
+      while (++index < length) {
+        result[index] = isNil ? undefined : get(object, paths[index]);
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.clamp` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     */
+    function baseClamp(number, lower, upper) {
+      if (number === number) {
+        if (upper !== undefined) {
+          number = number <= upper ? number : upper;
+        }
+        if (lower !== undefined) {
+          number = number >= lower ? number : lower;
+        }
+      }
+      return number;
+    }
+
+    /**
+     * The base implementation of `_.clone` and `_.cloneDeep` which tracks
+     * traversed objects.
+     *
+     * @private
+     * @param {*} value The value to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @param {boolean} [isFull] Specify a clone including symbols.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @param {string} [key] The key of `value`.
+     * @param {Object} [object] The parent object of `value`.
+     * @param {Object} [stack] Tracks traversed objects and their clone counterparts.
+     * @returns {*} Returns the cloned value.
+     */
+    function baseClone(value, isDeep, isFull, customizer, key, object, stack) {
+      var result;
+      if (customizer) {
+        result = object ? customizer(value, key, object, stack) : customizer(value);
+      }
+      if (result !== undefined) {
+        return result;
+      }
+      if (!isObject(value)) {
+        return value;
+      }
+      var isArr = isArray(value);
+      if (isArr) {
+        result = initCloneArray(value);
+        if (!isDeep) {
+          return copyArray(value, result);
+        }
+      } else {
+        var tag = getTag(value),
+            isFunc = tag == funcTag || tag == genTag;
+
+        if (isBuffer(value)) {
+          return cloneBuffer(value, isDeep);
+        }
+        if (tag == objectTag || tag == argsTag || (isFunc && !object)) {
+          if (isHostObject(value)) {
+            return object ? value : {};
+          }
+          result = initCloneObject(isFunc ? {} : value);
+          if (!isDeep) {
+            return copySymbols(value, baseAssign(result, value));
+          }
+        } else {
+          if (!cloneableTags[tag]) {
+            return object ? value : {};
+          }
+          result = initCloneByTag(value, tag, baseClone, isDeep);
+        }
+      }
+      // Check for circular references and return its corresponding clone.
+      stack || (stack = new Stack);
+      var stacked = stack.get(value);
+      if (stacked) {
+        return stacked;
+      }
+      stack.set(value, result);
+
+      if (!isArr) {
+        var props = isFull ? getAllKeys(value) : keys(value);
+      }
+      // Recursively populate clone (susceptible to call stack limits).
+      arrayEach(props || value, function(subValue, key) {
+        if (props) {
+          key = subValue;
+          subValue = value[key];
+        }
+        assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack));
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.conforms` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new function.
+     */
+    function baseConforms(source) {
+      var props = keys(source),
+          length = props.length;
+
+      return function(object) {
+        if (object == null) {
+          return !length;
+        }
+        var index = length;
+        while (index--) {
+          var key = props[index],
+              predicate = source[key],
+              value = object[key];
+
+          if ((value === undefined &&
+              !(key in Object(object))) || !predicate(value)) {
+            return false;
+          }
+        }
+        return true;
+      };
+    }
+
+    /**
+     * The base implementation of `_.create` without support for assigning
+     * properties to the created object.
+     *
+     * @private
+     * @param {Object} prototype The object to inherit from.
+     * @returns {Object} Returns the new object.
+     */
+    function baseCreate(proto) {
+      return isObject(proto) ? objectCreate(proto) : {};
+    }
+
+    /**
+     * The base implementation of `_.delay` and `_.defer` which accepts an array
+     * of `func` arguments.
+     *
+     * @private
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {Object} args The arguments to provide to `func`.
+     * @returns {number} Returns the timer id.
+     */
+    function baseDelay(func, wait, args) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return setTimeout(function() { func.apply(undefined, args); }, wait);
+    }
+
+    /**
+     * The base implementation of methods like `_.difference` without support
+     * for excluding multiple arrays or iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Array} values The values to exclude.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     */
+    function baseDifference(array, values, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          isCommon = true,
+          length = array.length,
+          result = [],
+          valuesLength = values.length;
+
+      if (!length) {
+        return result;
+      }
+      if (iteratee) {
+        values = arrayMap(values, baseUnary(iteratee));
+      }
+      if (comparator) {
+        includes = arrayIncludesWith;
+        isCommon = false;
+      }
+      else if (values.length >= LARGE_ARRAY_SIZE) {
+        includes = cacheHas;
+        isCommon = false;
+        values = new SetCache(values);
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var valuesIndex = valuesLength;
+          while (valuesIndex--) {
+            if (values[valuesIndex] === computed) {
+              continue outer;
+            }
+          }
+          result.push(value);
+        }
+        else if (!includes(values, computed, comparator)) {
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.forEach` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEach = createBaseEach(baseForOwn);
+
+    /**
+     * The base implementation of `_.forEachRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     */
+    var baseEachRight = createBaseEach(baseForOwnRight, true);
+
+    /**
+     * The base implementation of `_.every` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`
+     */
+    function baseEvery(collection, predicate) {
+      var result = true;
+      baseEach(collection, function(value, index, collection) {
+        result = !!predicate(value, index, collection);
+        return result;
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of methods like `_.max` and `_.min` which accepts a
+     * `comparator` to determine the extremum value.
+     *
+     * @private
+     * @param {Array} array The array to iterate over.
+     * @param {Function} iteratee The iteratee invoked per iteration.
+     * @param {Function} comparator The comparator used to compare values.
+     * @returns {*} Returns the extremum value.
+     */
+    function baseExtremum(array, iteratee, comparator) {
+      var index = -1,
+          length = array.length;
+
+      while (++index < length) {
+        var value = array[index],
+            current = iteratee(value);
+
+        if (current != null && (computed === undefined
+              ? (current === current && !isSymbol(current))
+              : comparator(current, computed)
+            )) {
+          var computed = current,
+              result = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.fill` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     */
+    function baseFill(array, value, start, end) {
+      var length = array.length;
+
+      start = toInteger(start);
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = (end === undefined || end > length) ? length : toInteger(end);
+      if (end < 0) {
+        end += length;
+      }
+      end = start > end ? 0 : toLength(end);
+      while (start < end) {
+        array[start++] = value;
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.filter` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     */
+    function baseFilter(collection, predicate) {
+      var result = [];
+      baseEach(collection, function(value, index, collection) {
+        if (predicate(value, index, collection)) {
+          result.push(value);
+        }
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.flatten` with support for restricting flattening.
+     *
+     * @private
+     * @param {Array} array The array to flatten.
+     * @param {number} depth The maximum recursion depth.
+     * @param {boolean} [predicate=isFlattenable] The function invoked per iteration.
+     * @param {boolean} [isStrict] Restrict to values that pass `predicate` checks.
+     * @param {Array} [result=[]] The initial result value.
+     * @returns {Array} Returns the new flattened array.
+     */
+    function baseFlatten(array, depth, predicate, isStrict, result) {
+      var index = -1,
+          length = array.length;
+
+      predicate || (predicate = isFlattenable);
+      result || (result = []);
+
+      while (++index < length) {
+        var value = array[index];
+        if (depth > 0 && predicate(value)) {
+          if (depth > 1) {
+            // Recursively flatten arrays (susceptible to call stack limits).
+            baseFlatten(value, depth - 1, predicate, isStrict, result);
+          } else {
+            arrayPush(result, value);
+          }
+        } else if (!isStrict) {
+          result[result.length] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `baseForOwn` which iterates over `object`
+     * properties returned by `keysFunc` and invokes `iteratee` for each property.
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseFor = createBaseFor();
+
+    /**
+     * This function is like `baseFor` except that it iterates over properties
+     * in the opposite order.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @returns {Object} Returns `object`.
+     */
+    var baseForRight = createBaseFor(true);
+
+    /**
+     * The base implementation of `_.forOwn` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwn(object, iteratee) {
+      return object && baseFor(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.forOwnRight` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     */
+    function baseForOwnRight(object, iteratee) {
+      return object && baseForRight(object, iteratee, keys);
+    }
+
+    /**
+     * The base implementation of `_.functions` which creates an array of
+     * `object` function property names filtered from `props`.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Array} props The property names to filter.
+     * @returns {Array} Returns the new array of filtered property names.
+     */
+    function baseFunctions(object, props) {
+      return arrayFilter(props, function(key) {
+        return isFunction(object[key]);
+      });
+    }
+
+    /**
+     * The base implementation of `_.get` without support for default values.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseGet(object, path) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var index = 0,
+          length = path.length;
+
+      while (object != null && index < length) {
+        object = object[toKey(path[index++])];
+      }
+      return (index && index == length) ? object : undefined;
+    }
+
+    /**
+     * The base implementation of `getAllKeys` and `getAllKeysIn` which uses
+     * `keysFunc` and `symbolsFunc` to get the enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Function} keysFunc The function to get the keys of `object`.
+     * @param {Function} symbolsFunc The function to get the symbols of `object`.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function baseGetAllKeys(object, keysFunc, symbolsFunc) {
+      var result = keysFunc(object);
+      return isArray(object)
+        ? result
+        : arrayPush(result, symbolsFunc(object));
+    }
+
+    /**
+     * The base implementation of `_.gt` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     */
+    function baseGt(value, other) {
+      return value > other;
+    }
+
+    /**
+     * The base implementation of `_.has` without support for deep paths.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHas(object, key) {
+      // Avoid a bug in IE 10-11 where objects with a [[Prototype]] of `null`,
+      // that are composed entirely of index properties, return `false` for
+      // `hasOwnProperty` checks of them.
+      return hasOwnProperty.call(object, key) ||
+        (typeof object == 'object' && key in object && getPrototype(object) === null);
+    }
+
+    /**
+     * The base implementation of `_.hasIn` without support for deep paths.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} key The key to check.
+     * @returns {boolean} Returns `true` if `key` exists, else `false`.
+     */
+    function baseHasIn(object, key) {
+      return key in Object(object);
+    }
+
+    /**
+     * The base implementation of `_.inRange` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {number} number The number to check.
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     */
+    function baseInRange(number, start, end) {
+      return number >= nativeMin(start, end) && number < nativeMax(start, end);
+    }
+
+    /**
+     * The base implementation of methods like `_.intersection`, without support
+     * for iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of shared values.
+     */
+    function baseIntersection(arrays, iteratee, comparator) {
+      var includes = comparator ? arrayIncludesWith : arrayIncludes,
+          length = arrays[0].length,
+          othLength = arrays.length,
+          othIndex = othLength,
+          caches = Array(othLength),
+          maxLength = Infinity,
+          result = [];
+
+      while (othIndex--) {
+        var array = arrays[othIndex];
+        if (othIndex && iteratee) {
+          array = arrayMap(array, baseUnary(iteratee));
+        }
+        maxLength = nativeMin(array.length, maxLength);
+        caches[othIndex] = !comparator && (iteratee || (length >= 120 && array.length >= 120))
+          ? new SetCache(othIndex && array)
+          : undefined;
+      }
+      array = arrays[0];
+
+      var index = -1,
+          seen = caches[0];
+
+      outer:
+      while (++index < length && result.length < maxLength) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (!(seen
+              ? cacheHas(seen, computed)
+              : includes(result, computed, comparator)
+            )) {
+          othIndex = othLength;
+          while (--othIndex) {
+            var cache = caches[othIndex];
+            if (!(cache
+                  ? cacheHas(cache, computed)
+                  : includes(arrays[othIndex], computed, comparator))
+                ) {
+              continue outer;
+            }
+          }
+          if (seen) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.invert` and `_.invertBy` which inverts
+     * `object` with values transformed by `iteratee` and set by `setter`.
+     *
+     * @private
+     * @param {Object} object The object to iterate over.
+     * @param {Function} setter The function to set `accumulator` values.
+     * @param {Function} iteratee The iteratee to transform values.
+     * @param {Object} accumulator The initial inverted object.
+     * @returns {Function} Returns `accumulator`.
+     */
+    function baseInverter(object, setter, iteratee, accumulator) {
+      baseForOwn(object, function(value, key, object) {
+        setter(accumulator, iteratee(value), key, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * The base implementation of `_.invoke` without support for individual
+     * method arguments.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {Array} args The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     */
+    function baseInvoke(object, path, args) {
+      if (!isKey(path, object)) {
+        path = castPath(path);
+        object = parent(object, path);
+        path = last(path);
+      }
+      var func = object == null ? object : object[toKey(path)];
+      return func == null ? undefined : apply(func, object, args);
+    }
+
+    /**
+     * The base implementation of `_.isEqual` which supports partial comparisons
+     * and tracks traversed objects.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @param {boolean} [bitmask] The bitmask of comparison flags.
+     *  The bitmask may be composed of the following flags:
+     *     1 - Unordered comparison
+     *     2 - Partial comparison
+     * @param {Object} [stack] Tracks traversed `value` and `other` objects.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     */
+    function baseIsEqual(value, other, customizer, bitmask, stack) {
+      if (value === other) {
+        return true;
+      }
+      if (value == null || other == null || (!isObject(value) && !isObjectLike(other))) {
+        return value !== value && other !== other;
+      }
+      return baseIsEqualDeep(value, other, baseIsEqual, customizer, bitmask, stack);
+    }
+
+    /**
+     * A specialized version of `baseIsEqual` for arrays and objects which performs
+     * deep comparisons and tracks traversed objects enabling objects with circular
+     * references to be compared.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @param {number} [bitmask] The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} [stack] Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function baseIsEqualDeep(object, other, equalFunc, customizer, bitmask, stack) {
+      var objIsArr = isArray(object),
+          othIsArr = isArray(other),
+          objTag = arrayTag,
+          othTag = arrayTag;
+
+      if (!objIsArr) {
+        objTag = getTag(object);
+        objTag = objTag == argsTag ? objectTag : objTag;
+      }
+      if (!othIsArr) {
+        othTag = getTag(other);
+        othTag = othTag == argsTag ? objectTag : othTag;
+      }
+      var objIsObj = objTag == objectTag && !isHostObject(object),
+          othIsObj = othTag == objectTag && !isHostObject(other),
+          isSameTag = objTag == othTag;
+
+      if (isSameTag && !objIsObj) {
+        stack || (stack = new Stack);
+        return (objIsArr || isTypedArray(object))
+          ? equalArrays(object, other, equalFunc, customizer, bitmask, stack)
+          : equalByTag(object, other, objTag, equalFunc, customizer, bitmask, stack);
+      }
+      if (!(bitmask & PARTIAL_COMPARE_FLAG)) {
+        var objIsWrapped = objIsObj && hasOwnProperty.call(object, '__wrapped__'),
+            othIsWrapped = othIsObj && hasOwnProperty.call(other, '__wrapped__');
+
+        if (objIsWrapped || othIsWrapped) {
+          var objUnwrapped = objIsWrapped ? object.value() : object,
+              othUnwrapped = othIsWrapped ? other.value() : other;
+
+          stack || (stack = new Stack);
+          return equalFunc(objUnwrapped, othUnwrapped, customizer, bitmask, stack);
+        }
+      }
+      if (!isSameTag) {
+        return false;
+      }
+      stack || (stack = new Stack);
+      return equalObjects(object, other, equalFunc, customizer, bitmask, stack);
+    }
+
+    /**
+     * The base implementation of `_.isMatch` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Array} matchData The property names, values, and compare flags to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     */
+    function baseIsMatch(object, source, matchData, customizer) {
+      var index = matchData.length,
+          length = index,
+          noCustomizer = !customizer;
+
+      if (object == null) {
+        return !length;
+      }
+      object = Object(object);
+      while (index--) {
+        var data = matchData[index];
+        if ((noCustomizer && data[2])
+              ? data[1] !== object[data[0]]
+              : !(data[0] in object)
+            ) {
+          return false;
+        }
+      }
+      while (++index < length) {
+        data = matchData[index];
+        var key = data[0],
+            objValue = object[key],
+            srcValue = data[1];
+
+        if (noCustomizer && data[2]) {
+          if (objValue === undefined && !(key in object)) {
+            return false;
+          }
+        } else {
+          var stack = new Stack;
+          if (customizer) {
+            var result = customizer(objValue, srcValue, key, object, source, stack);
+          }
+          if (!(result === undefined
+                ? baseIsEqual(srcValue, objValue, customizer, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG, stack)
+                : result
+              )) {
+            return false;
+          }
+        }
+      }
+      return true;
+    }
+
+    /**
+     * The base implementation of `_.iteratee`.
+     *
+     * @private
+     * @param {*} [value=_.identity] The value to convert to an iteratee.
+     * @returns {Function} Returns the iteratee.
+     */
+    function baseIteratee(value) {
+      // Don't store the `typeof` result in a variable to avoid a JIT bug in Safari 9.
+      // See https://bugs.webkit.org/show_bug.cgi?id=156034 for more details.
+      if (typeof value == 'function') {
+        return value;
+      }
+      if (value == null) {
+        return identity;
+      }
+      if (typeof value == 'object') {
+        return isArray(value)
+          ? baseMatchesProperty(value[0], value[1])
+          : baseMatches(value);
+      }
+      return property(value);
+    }
+
+    /**
+     * The base implementation of `_.keys` which doesn't skip the constructor
+     * property of prototypes or treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeys(object) {
+      return nativeKeys(Object(object));
+    }
+
+    /**
+     * The base implementation of `_.keysIn` which doesn't skip the constructor
+     * property of prototypes or treat sparse arrays as dense.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     */
+    function baseKeysIn(object) {
+      object = object == null ? object : Object(object);
+
+      var result = [];
+      for (var key in object) {
+        result.push(key);
+      }
+      return result;
+    }
+
+    // Fallback for IE < 9 with es6-shim.
+    if (enumerate && !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf')) {
+      baseKeysIn = function(object) {
+        return iteratorToArray(enumerate(object));
+      };
+    }
+
+    /**
+     * The base implementation of `_.lt` which doesn't coerce arguments to numbers.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     */
+    function baseLt(value, other) {
+      return value < other;
+    }
+
+    /**
+     * The base implementation of `_.map` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} iteratee The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     */
+    function baseMap(collection, iteratee) {
+      var index = -1,
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value, key, collection) {
+        result[++index] = iteratee(value, key, collection);
+      });
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.matches` which doesn't clone `source`.
+     *
+     * @private
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new function.
+     */
+    function baseMatches(source) {
+      var matchData = getMatchData(source);
+      if (matchData.length == 1 && matchData[0][2]) {
+        return matchesStrictComparable(matchData[0][0], matchData[0][1]);
+      }
+      return function(object) {
+        return object === source || baseIsMatch(object, source, matchData);
+      };
+    }
+
+    /**
+     * The base implementation of `_.matchesProperty` which doesn't clone `srcValue`.
+     *
+     * @private
+     * @param {string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     */
+    function baseMatchesProperty(path, srcValue) {
+      if (isKey(path) && isStrictComparable(srcValue)) {
+        return matchesStrictComparable(toKey(path), srcValue);
+      }
+      return function(object) {
+        var objValue = get(object, path);
+        return (objValue === undefined && objValue === srcValue)
+          ? hasIn(object, path)
+          : baseIsEqual(srcValue, objValue, undefined, UNORDERED_COMPARE_FLAG | PARTIAL_COMPARE_FLAG);
+      };
+    }
+
+    /**
+     * The base implementation of `_.merge` without support for multiple sources.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} [customizer] The function to customize merged values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMerge(object, source, srcIndex, customizer, stack) {
+      if (object === source) {
+        return;
+      }
+      if (!(isArray(source) || isTypedArray(source))) {
+        var props = keysIn(source);
+      }
+      arrayEach(props || source, function(srcValue, key) {
+        if (props) {
+          key = srcValue;
+          srcValue = source[key];
+        }
+        if (isObject(srcValue)) {
+          stack || (stack = new Stack);
+          baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack);
+        }
+        else {
+          var newValue = customizer
+            ? customizer(object[key], srcValue, (key + ''), object, source, stack)
+            : undefined;
+
+          if (newValue === undefined) {
+            newValue = srcValue;
+          }
+          assignMergeValue(object, key, newValue);
+        }
+      });
+    }
+
+    /**
+     * A specialized version of `baseMerge` for arrays and objects which performs
+     * deep merges and tracks traversed objects enabling objects with circular
+     * references to be merged.
+     *
+     * @private
+     * @param {Object} object The destination object.
+     * @param {Object} source The source object.
+     * @param {string} key The key of the value to merge.
+     * @param {number} srcIndex The index of `source`.
+     * @param {Function} mergeFunc The function to merge values.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     */
+    function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) {
+      var objValue = object[key],
+          srcValue = source[key],
+          stacked = stack.get(srcValue);
+
+      if (stacked) {
+        assignMergeValue(object, key, stacked);
+        return;
+      }
+      var newValue = customizer
+        ? customizer(objValue, srcValue, (key + ''), object, source, stack)
+        : undefined;
+
+      var isCommon = newValue === undefined;
+
+      if (isCommon) {
+        newValue = srcValue;
+        if (isArray(srcValue) || isTypedArray(srcValue)) {
+          if (isArray(objValue)) {
+            newValue = objValue;
+          }
+          else if (isArrayLikeObject(objValue)) {
+            newValue = copyArray(objValue);
+          }
+          else {
+            isCommon = false;
+            newValue = baseClone(srcValue, true);
+          }
+        }
+        else if (isPlainObject(srcValue) || isArguments(srcValue)) {
+          if (isArguments(objValue)) {
+            newValue = toPlainObject(objValue);
+          }
+          else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) {
+            isCommon = false;
+            newValue = baseClone(srcValue, true);
+          }
+          else {
+            newValue = objValue;
+          }
+        }
+        else {
+          isCommon = false;
+        }
+      }
+      stack.set(srcValue, newValue);
+
+      if (isCommon) {
+        // Recursively merge objects and arrays (susceptible to call stack limits).
+        mergeFunc(newValue, srcValue, srcIndex, customizer, stack);
+      }
+      stack['delete'](srcValue);
+      assignMergeValue(object, key, newValue);
+    }
+
+    /**
+     * The base implementation of `_.nth` which doesn't coerce `n` to an integer.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {number} n The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     */
+    function baseNth(array, n) {
+      var length = array.length;
+      if (!length) {
+        return;
+      }
+      n += n < 0 ? length : 0;
+      return isIndex(n, length) ? array[n] : undefined;
+    }
+
+    /**
+     * The base implementation of `_.orderBy` without param guards.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function[]|Object[]|string[]} iteratees The iteratees to sort by.
+     * @param {string[]} orders The sort orders of `iteratees`.
+     * @returns {Array} Returns the new sorted array.
+     */
+    function baseOrderBy(collection, iteratees, orders) {
+      var index = -1;
+      iteratees = arrayMap(iteratees.length ? iteratees : [identity], baseUnary(getIteratee()));
+
+      var result = baseMap(collection, function(value, key, collection) {
+        var criteria = arrayMap(iteratees, function(iteratee) {
+          return iteratee(value);
+        });
+        return { 'criteria': criteria, 'index': ++index, 'value': value };
+      });
+
+      return baseSortBy(result, function(object, other) {
+        return compareMultiple(object, other, orders);
+      });
+    }
+
+    /**
+     * The base implementation of `_.pick` without support for individual
+     * property identifiers.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {string[]} props The property identifiers to pick.
+     * @returns {Object} Returns the new object.
+     */
+    function basePick(object, props) {
+      object = Object(object);
+      return arrayReduce(props, function(result, key) {
+        if (key in object) {
+          result[key] = object[key];
+        }
+        return result;
+      }, {});
+    }
+
+    /**
+     * The base implementation of  `_.pickBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Object} object The source object.
+     * @param {Function} predicate The function invoked per property.
+     * @returns {Object} Returns the new object.
+     */
+    function basePickBy(object, predicate) {
+      var index = -1,
+          props = getAllKeysIn(object),
+          length = props.length,
+          result = {};
+
+      while (++index < length) {
+        var key = props[index],
+            value = object[key];
+
+        if (predicate(value, key)) {
+          result[key] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.property` without support for deep paths.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @returns {Function} Returns the new function.
+     */
+    function baseProperty(key) {
+      return function(object) {
+        return object == null ? undefined : object[key];
+      };
+    }
+
+    /**
+     * A specialized version of `baseProperty` which supports deep paths.
+     *
+     * @private
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new function.
+     */
+    function basePropertyDeep(path) {
+      return function(object) {
+        return baseGet(object, path);
+      };
+    }
+
+    /**
+     * The base implementation of `_.pullAllBy` without support for iteratee
+     * shorthands.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAll(array, values, iteratee, comparator) {
+      var indexOf = comparator ? baseIndexOfWith : baseIndexOf,
+          index = -1,
+          length = values.length,
+          seen = array;
+
+      if (iteratee) {
+        seen = arrayMap(array, baseUnary(iteratee));
+      }
+      while (++index < length) {
+        var fromIndex = 0,
+            value = values[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        while ((fromIndex = indexOf(seen, computed, fromIndex, comparator)) > -1) {
+          if (seen !== array) {
+            splice.call(seen, fromIndex, 1);
+          }
+          splice.call(array, fromIndex, 1);
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.pullAt` without support for individual
+     * indexes or capturing the removed elements.
+     *
+     * @private
+     * @param {Array} array The array to modify.
+     * @param {number[]} indexes The indexes of elements to remove.
+     * @returns {Array} Returns `array`.
+     */
+    function basePullAt(array, indexes) {
+      var length = array ? indexes.length : 0,
+          lastIndex = length - 1;
+
+      while (length--) {
+        var index = indexes[length];
+        if (length == lastIndex || index !== previous) {
+          var previous = index;
+          if (isIndex(index)) {
+            splice.call(array, index, 1);
+          }
+          else if (!isKey(index, array)) {
+            var path = castPath(index),
+                object = parent(array, path);
+
+            if (object != null) {
+              delete object[toKey(last(path))];
+            }
+          }
+          else {
+            delete array[toKey(index)];
+          }
+        }
+      }
+      return array;
+    }
+
+    /**
+     * The base implementation of `_.random` without support for returning
+     * floating-point numbers.
+     *
+     * @private
+     * @param {number} lower The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the random number.
+     */
+    function baseRandom(lower, upper) {
+      return lower + nativeFloor(nativeRandom() * (upper - lower + 1));
+    }
+
+    /**
+     * The base implementation of `_.range` and `_.rangeRight` which doesn't
+     * coerce arguments to numbers.
+     *
+     * @private
+     * @param {number} start The start of the range.
+     * @param {number} end The end of the range.
+     * @param {number} step The value to increment or decrement by.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the new array of numbers.
+     */
+    function baseRange(start, end, step, fromRight) {
+      var index = -1,
+          length = nativeMax(nativeCeil((end - start) / (step || 1)), 0),
+          result = Array(length);
+
+      while (length--) {
+        result[fromRight ? length : ++index] = start;
+        start += step;
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.repeat` which doesn't coerce arguments.
+     *
+     * @private
+     * @param {string} string The string to repeat.
+     * @param {number} n The number of times to repeat the string.
+     * @returns {string} Returns the repeated string.
+     */
+    function baseRepeat(string, n) {
+      var result = '';
+      if (!string || n < 1 || n > MAX_SAFE_INTEGER) {
+        return result;
+      }
+      // Leverage the exponentiation by squaring algorithm for a faster repeat.
+      // See https://en.wikipedia.org/wiki/Exponentiation_by_squaring for more details.
+      do {
+        if (n % 2) {
+          result += string;
+        }
+        n = nativeFloor(n / 2);
+        if (n) {
+          string += string;
+        }
+      } while (n);
+
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.set`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseSet(object, path, value, customizer) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var index = -1,
+          length = path.length,
+          lastIndex = length - 1,
+          nested = object;
+
+      while (nested != null && ++index < length) {
+        var key = toKey(path[index]);
+        if (isObject(nested)) {
+          var newValue = value;
+          if (index != lastIndex) {
+            var objValue = nested[key];
+            newValue = customizer ? customizer(objValue, key, nested) : undefined;
+            if (newValue === undefined) {
+              newValue = objValue == null
+                ? (isIndex(path[index + 1]) ? [] : {})
+                : objValue;
+            }
+          }
+          assignValue(nested, key, newValue);
+        }
+        nested = nested[key];
+      }
+      return object;
+    }
+
+    /**
+     * The base implementation of `setData` without support for hot loop detection.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var baseSetData = !metaMap ? identity : function(func, data) {
+      metaMap.set(func, data);
+      return func;
+    };
+
+    /**
+     * The base implementation of `_.slice` without an iteratee call guard.
+     *
+     * @private
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseSlice(array, start, end) {
+      var index = -1,
+          length = array.length;
+
+      if (start < 0) {
+        start = -start > length ? 0 : (length + start);
+      }
+      end = end > length ? length : end;
+      if (end < 0) {
+        end += length;
+      }
+      length = start > end ? 0 : ((end - start) >>> 0);
+      start >>>= 0;
+
+      var result = Array(length);
+      while (++index < length) {
+        result[index] = array[index + start];
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.some` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} predicate The function invoked per iteration.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     */
+    function baseSome(collection, predicate) {
+      var result;
+
+      baseEach(collection, function(value, index, collection) {
+        result = predicate(value, index, collection);
+        return !result;
+      });
+      return !!result;
+    }
+
+    /**
+     * The base implementation of `_.sortedIndex` and `_.sortedLastIndex` which
+     * performs a binary search of `array` to determine the index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndex(array, value, retHighest) {
+      var low = 0,
+          high = array ? array.length : low;
+
+      if (typeof value == 'number' && value === value && high <= HALF_MAX_ARRAY_LENGTH) {
+        while (low < high) {
+          var mid = (low + high) >>> 1,
+              computed = array[mid];
+
+          if (computed !== null && !isSymbol(computed) &&
+              (retHighest ? (computed <= value) : (computed < value))) {
+            low = mid + 1;
+          } else {
+            high = mid;
+          }
+        }
+        return high;
+      }
+      return baseSortedIndexBy(array, value, identity, retHighest);
+    }
+
+    /**
+     * The base implementation of `_.sortedIndexBy` and `_.sortedLastIndexBy`
+     * which invokes `iteratee` for `value` and each element of `array` to compute
+     * their sort ranking. The iteratee is invoked with one argument; (value).
+     *
+     * @private
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Function} iteratee The iteratee invoked per element.
+     * @param {boolean} [retHighest] Specify returning the highest qualified index.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     */
+    function baseSortedIndexBy(array, value, iteratee, retHighest) {
+      value = iteratee(value);
+
+      var low = 0,
+          high = array ? array.length : 0,
+          valIsNaN = value !== value,
+          valIsNull = value === null,
+          valIsSymbol = isSymbol(value),
+          valIsUndefined = value === undefined;
+
+      while (low < high) {
+        var mid = nativeFloor((low + high) / 2),
+            computed = iteratee(array[mid]),
+            othIsDefined = computed !== undefined,
+            othIsNull = computed === null,
+            othIsReflexive = computed === computed,
+            othIsSymbol = isSymbol(computed);
+
+        if (valIsNaN) {
+          var setLow = retHighest || othIsReflexive;
+        } else if (valIsUndefined) {
+          setLow = othIsReflexive && (retHighest || othIsDefined);
+        } else if (valIsNull) {
+          setLow = othIsReflexive && othIsDefined && (retHighest || !othIsNull);
+        } else if (valIsSymbol) {
+          setLow = othIsReflexive && othIsDefined && !othIsNull && (retHighest || !othIsSymbol);
+        } else if (othIsNull || othIsSymbol) {
+          setLow = false;
+        } else {
+          setLow = retHighest ? (computed <= value) : (computed < value);
+        }
+        if (setLow) {
+          low = mid + 1;
+        } else {
+          high = mid;
+        }
+      }
+      return nativeMin(high, MAX_ARRAY_INDEX);
+    }
+
+    /**
+     * The base implementation of `_.sortedUniq` and `_.sortedUniqBy` without
+     * support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseSortedUniq(array, iteratee) {
+      var index = -1,
+          length = array.length,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        if (!index || !eq(computed, seen)) {
+          var seen = computed;
+          result[resIndex++] = value === 0 ? 0 : value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.toNumber` which doesn't ensure correct
+     * conversions of binary, hexadecimal, or octal string values.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     */
+    function baseToNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      return +value;
+    }
+
+    /**
+     * The base implementation of `_.toString` which doesn't convert nullish
+     * values to empty strings.
+     *
+     * @private
+     * @param {*} value The value to process.
+     * @returns {string} Returns the string.
+     */
+    function baseToString(value) {
+      // Exit early for strings to avoid a performance hit in some environments.
+      if (typeof value == 'string') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return symbolToString ? symbolToString.call(value) : '';
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * The base implementation of `_.uniqBy` without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     */
+    function baseUniq(array, iteratee, comparator) {
+      var index = -1,
+          includes = arrayIncludes,
+          length = array.length,
+          isCommon = true,
+          result = [],
+          seen = result;
+
+      if (comparator) {
+        isCommon = false;
+        includes = arrayIncludesWith;
+      }
+      else if (length >= LARGE_ARRAY_SIZE) {
+        var set = iteratee ? null : createSet(array);
+        if (set) {
+          return setToArray(set);
+        }
+        isCommon = false;
+        includes = cacheHas;
+        seen = new SetCache;
+      }
+      else {
+        seen = iteratee ? [] : result;
+      }
+      outer:
+      while (++index < length) {
+        var value = array[index],
+            computed = iteratee ? iteratee(value) : value;
+
+        value = (comparator || value !== 0) ? value : 0;
+        if (isCommon && computed === computed) {
+          var seenIndex = seen.length;
+          while (seenIndex--) {
+            if (seen[seenIndex] === computed) {
+              continue outer;
+            }
+          }
+          if (iteratee) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+        else if (!includes(seen, computed, comparator)) {
+          if (seen !== result) {
+            seen.push(computed);
+          }
+          result.push(value);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The base implementation of `_.unset`.
+     *
+     * @private
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     */
+    function baseUnset(object, path) {
+      path = isKey(path, object) ? [path] : castPath(path);
+      object = parent(object, path);
+
+      var key = toKey(last(path));
+      return !(object != null && baseHas(object, key)) || delete object[key];
+    }
+
+    /**
+     * The base implementation of `_.update`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to update.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize path creation.
+     * @returns {Object} Returns `object`.
+     */
+    function baseUpdate(object, path, updater, customizer) {
+      return baseSet(object, path, updater(baseGet(object, path)), customizer);
+    }
+
+    /**
+     * The base implementation of methods like `_.dropWhile` and `_.takeWhile`
+     * without support for iteratee shorthands.
+     *
+     * @private
+     * @param {Array} array The array to query.
+     * @param {Function} predicate The function invoked per iteration.
+     * @param {boolean} [isDrop] Specify dropping elements instead of taking them.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function baseWhile(array, predicate, isDrop, fromRight) {
+      var length = array.length,
+          index = fromRight ? length : -1;
+
+      while ((fromRight ? index-- : ++index < length) &&
+        predicate(array[index], index, array)) {}
+
+      return isDrop
+        ? baseSlice(array, (fromRight ? 0 : index), (fromRight ? index + 1 : length))
+        : baseSlice(array, (fromRight ? index + 1 : 0), (fromRight ? length : index));
+    }
+
+    /**
+     * The base implementation of `wrapperValue` which returns the result of
+     * performing a sequence of actions on the unwrapped `value`, where each
+     * successive action is supplied the return value of the previous.
+     *
+     * @private
+     * @param {*} value The unwrapped value.
+     * @param {Array} actions Actions to perform to resolve the unwrapped value.
+     * @returns {*} Returns the resolved value.
+     */
+    function baseWrapperValue(value, actions) {
+      var result = value;
+      if (result instanceof LazyWrapper) {
+        result = result.value();
+      }
+      return arrayReduce(actions, function(result, action) {
+        return action.func.apply(action.thisArg, arrayPush([result], action.args));
+      }, result);
+    }
+
+    /**
+     * The base implementation of methods like `_.xor`, without support for
+     * iteratee shorthands, that accepts an array of arrays to inspect.
+     *
+     * @private
+     * @param {Array} arrays The arrays to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of values.
+     */
+    function baseXor(arrays, iteratee, comparator) {
+      var index = -1,
+          length = arrays.length;
+
+      while (++index < length) {
+        var result = result
+          ? arrayPush(
+              baseDifference(result, arrays[index], iteratee, comparator),
+              baseDifference(arrays[index], result, iteratee, comparator)
+            )
+          : arrays[index];
+      }
+      return (result && result.length) ? baseUniq(result, iteratee, comparator) : [];
+    }
+
+    /**
+     * This base implementation of `_.zipObject` which assigns values using `assignFunc`.
+     *
+     * @private
+     * @param {Array} props The property identifiers.
+     * @param {Array} values The property values.
+     * @param {Function} assignFunc The function to assign values.
+     * @returns {Object} Returns the new object.
+     */
+    function baseZipObject(props, values, assignFunc) {
+      var index = -1,
+          length = props.length,
+          valsLength = values.length,
+          result = {};
+
+      while (++index < length) {
+        var value = index < valsLength ? values[index] : undefined;
+        assignFunc(result, props[index], value);
+      }
+      return result;
+    }
+
+    /**
+     * Casts `value` to an empty array if it's not an array like object.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Array|Object} Returns the cast array-like object.
+     */
+    function castArrayLikeObject(value) {
+      return isArrayLikeObject(value) ? value : [];
+    }
+
+    /**
+     * Casts `value` to `identity` if it's not a function.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Function} Returns cast function.
+     */
+    function castFunction(value) {
+      return typeof value == 'function' ? value : identity;
+    }
+
+    /**
+     * Casts `value` to a path array if it's not one.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {Array} Returns the cast property path array.
+     */
+    function castPath(value) {
+      return isArray(value) ? value : stringToPath(value);
+    }
+
+    /**
+     * Casts `array` to a slice if it's needed.
+     *
+     * @private
+     * @param {Array} array The array to inspect.
+     * @param {number} start The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the cast slice.
+     */
+    function castSlice(array, start, end) {
+      var length = array.length;
+      end = end === undefined ? length : end;
+      return (!start && end >= length) ? array : baseSlice(array, start, end);
+    }
+
+    /**
+     * Creates a clone of  `buffer`.
+     *
+     * @private
+     * @param {Buffer} buffer The buffer to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Buffer} Returns the cloned buffer.
+     */
+    function cloneBuffer(buffer, isDeep) {
+      if (isDeep) {
+        return buffer.slice();
+      }
+      var result = new buffer.constructor(buffer.length);
+      buffer.copy(result);
+      return result;
+    }
+
+    /**
+     * Creates a clone of `arrayBuffer`.
+     *
+     * @private
+     * @param {ArrayBuffer} arrayBuffer The array buffer to clone.
+     * @returns {ArrayBuffer} Returns the cloned array buffer.
+     */
+    function cloneArrayBuffer(arrayBuffer) {
+      var result = new arrayBuffer.constructor(arrayBuffer.byteLength);
+      new Uint8Array(result).set(new Uint8Array(arrayBuffer));
+      return result;
+    }
+
+    /**
+     * Creates a clone of `dataView`.
+     *
+     * @private
+     * @param {Object} dataView The data view to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned data view.
+     */
+    function cloneDataView(dataView, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer;
+      return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength);
+    }
+
+    /**
+     * Creates a clone of `map`.
+     *
+     * @private
+     * @param {Object} map The map to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned map.
+     */
+    function cloneMap(map, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map);
+      return arrayReduce(array, addMapEntry, new map.constructor);
+    }
+
+    /**
+     * Creates a clone of `regexp`.
+     *
+     * @private
+     * @param {Object} regexp The regexp to clone.
+     * @returns {Object} Returns the cloned regexp.
+     */
+    function cloneRegExp(regexp) {
+      var result = new regexp.constructor(regexp.source, reFlags.exec(regexp));
+      result.lastIndex = regexp.lastIndex;
+      return result;
+    }
+
+    /**
+     * Creates a clone of `set`.
+     *
+     * @private
+     * @param {Object} set The set to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned set.
+     */
+    function cloneSet(set, isDeep, cloneFunc) {
+      var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set);
+      return arrayReduce(array, addSetEntry, new set.constructor);
+    }
+
+    /**
+     * Creates a clone of the `symbol` object.
+     *
+     * @private
+     * @param {Object} symbol The symbol object to clone.
+     * @returns {Object} Returns the cloned symbol object.
+     */
+    function cloneSymbol(symbol) {
+      return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {};
+    }
+
+    /**
+     * Creates a clone of `typedArray`.
+     *
+     * @private
+     * @param {Object} typedArray The typed array to clone.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the cloned typed array.
+     */
+    function cloneTypedArray(typedArray, isDeep) {
+      var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer;
+      return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length);
+    }
+
+    /**
+     * Compares values to sort them in ascending order.
+     *
+     * @private
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {number} Returns the sort order indicator for `value`.
+     */
+    function compareAscending(value, other) {
+      if (value !== other) {
+        var valIsDefined = value !== undefined,
+            valIsNull = value === null,
+            valIsReflexive = value === value,
+            valIsSymbol = isSymbol(value);
+
+        var othIsDefined = other !== undefined,
+            othIsNull = other === null,
+            othIsReflexive = other === other,
+            othIsSymbol = isSymbol(other);
+
+        if ((!othIsNull && !othIsSymbol && !valIsSymbol && value > other) ||
+            (valIsSymbol && othIsDefined && othIsReflexive && !othIsNull && !othIsSymbol) ||
+            (valIsNull && othIsDefined && othIsReflexive) ||
+            (!valIsDefined && othIsReflexive) ||
+            !valIsReflexive) {
+          return 1;
+        }
+        if ((!valIsNull && !valIsSymbol && !othIsSymbol && value < other) ||
+            (othIsSymbol && valIsDefined && valIsReflexive && !valIsNull && !valIsSymbol) ||
+            (othIsNull && valIsDefined && valIsReflexive) ||
+            (!othIsDefined && valIsReflexive) ||
+            !othIsReflexive) {
+          return -1;
+        }
+      }
+      return 0;
+    }
+
+    /**
+     * Used by `_.orderBy` to compare multiple properties of a value to another
+     * and stable sort them.
+     *
+     * If `orders` is unspecified, all values are sorted in ascending order. Otherwise,
+     * specify an order of "desc" for descending or "asc" for ascending sort order
+     * of corresponding values.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {boolean[]|string[]} orders The order to sort by for each property.
+     * @returns {number} Returns the sort order indicator for `object`.
+     */
+    function compareMultiple(object, other, orders) {
+      var index = -1,
+          objCriteria = object.criteria,
+          othCriteria = other.criteria,
+          length = objCriteria.length,
+          ordersLength = orders.length;
+
+      while (++index < length) {
+        var result = compareAscending(objCriteria[index], othCriteria[index]);
+        if (result) {
+          if (index >= ordersLength) {
+            return result;
+          }
+          var order = orders[index];
+          return result * (order == 'desc' ? -1 : 1);
+        }
+      }
+      // Fixes an `Array#sort` bug in the JS engine embedded in Adobe applications
+      // that causes it, under certain circumstances, to provide the same value for
+      // `object` and `other`. See https://github.com/jashkenas/underscore/pull/1247
+      // for more details.
+      //
+      // This also ensures a stable sort in V8 and other engines.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=90 for more details.
+      return object.index - other.index;
+    }
+
+    /**
+     * Creates an array that is the composition of partially applied arguments,
+     * placeholders, and provided arguments into a single array of arguments.
+     *
+     * @private
+     * @param {Array|Object} args The provided arguments.
+     * @param {Array} partials The arguments to prepend to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgs(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersLength = holders.length,
+          leftIndex = -1,
+          leftLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(leftLength + rangeLength),
+          isUncurried = !isCurried;
+
+      while (++leftIndex < leftLength) {
+        result[leftIndex] = partials[leftIndex];
+      }
+      while (++argsIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[holders[argsIndex]] = args[argsIndex];
+        }
+      }
+      while (rangeLength--) {
+        result[leftIndex++] = args[argsIndex++];
+      }
+      return result;
+    }
+
+    /**
+     * This function is like `composeArgs` except that the arguments composition
+     * is tailored for `_.partialRight`.
+     *
+     * @private
+     * @param {Array|Object} args The provided arguments.
+     * @param {Array} partials The arguments to append to those provided.
+     * @param {Array} holders The `partials` placeholder indexes.
+     * @params {boolean} [isCurried] Specify composing for a curried function.
+     * @returns {Array} Returns the new array of composed arguments.
+     */
+    function composeArgsRight(args, partials, holders, isCurried) {
+      var argsIndex = -1,
+          argsLength = args.length,
+          holdersIndex = -1,
+          holdersLength = holders.length,
+          rightIndex = -1,
+          rightLength = partials.length,
+          rangeLength = nativeMax(argsLength - holdersLength, 0),
+          result = Array(rangeLength + rightLength),
+          isUncurried = !isCurried;
+
+      while (++argsIndex < rangeLength) {
+        result[argsIndex] = args[argsIndex];
+      }
+      var offset = argsIndex;
+      while (++rightIndex < rightLength) {
+        result[offset + rightIndex] = partials[rightIndex];
+      }
+      while (++holdersIndex < holdersLength) {
+        if (isUncurried || argsIndex < argsLength) {
+          result[offset + holders[holdersIndex]] = args[argsIndex++];
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Copies the values of `source` to `array`.
+     *
+     * @private
+     * @param {Array} source The array to copy values from.
+     * @param {Array} [array=[]] The array to copy values to.
+     * @returns {Array} Returns `array`.
+     */
+    function copyArray(source, array) {
+      var index = -1,
+          length = source.length;
+
+      array || (array = Array(length));
+      while (++index < length) {
+        array[index] = source[index];
+      }
+      return array;
+    }
+
+    /**
+     * Copies properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy properties from.
+     * @param {Array} props The property identifiers to copy.
+     * @param {Object} [object={}] The object to copy properties to.
+     * @param {Function} [customizer] The function to customize copied values.
+     * @returns {Object} Returns `object`.
+     */
+    function copyObject(source, props, object, customizer) {
+      object || (object = {});
+
+      var index = -1,
+          length = props.length;
+
+      while (++index < length) {
+        var key = props[index];
+
+        var newValue = customizer
+          ? customizer(object[key], source[key], key, object, source)
+          : source[key];
+
+        assignValue(object, key, newValue);
+      }
+      return object;
+    }
+
+    /**
+     * Copies own symbol properties of `source` to `object`.
+     *
+     * @private
+     * @param {Object} source The object to copy symbols from.
+     * @param {Object} [object={}] The object to copy symbols to.
+     * @returns {Object} Returns `object`.
+     */
+    function copySymbols(source, object) {
+      return copyObject(source, getSymbols(source), object);
+    }
+
+    /**
+     * Creates a function like `_.groupBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} [initializer] The accumulator object initializer.
+     * @returns {Function} Returns the new aggregator function.
+     */
+    function createAggregator(setter, initializer) {
+      return function(collection, iteratee) {
+        var func = isArray(collection) ? arrayAggregator : baseAggregator,
+            accumulator = initializer ? initializer() : {};
+
+        return func(collection, setter, getIteratee(iteratee), accumulator);
+      };
+    }
+
+    /**
+     * Creates a function like `_.assign`.
+     *
+     * @private
+     * @param {Function} assigner The function to assign values.
+     * @returns {Function} Returns the new assigner function.
+     */
+    function createAssigner(assigner) {
+      return rest(function(object, sources) {
+        var index = -1,
+            length = sources.length,
+            customizer = length > 1 ? sources[length - 1] : undefined,
+            guard = length > 2 ? sources[2] : undefined;
+
+        customizer = typeof customizer == 'function'
+          ? (length--, customizer)
+          : undefined;
+
+        if (guard && isIterateeCall(sources[0], sources[1], guard)) {
+          customizer = length < 3 ? undefined : customizer;
+          length = 1;
+        }
+        object = Object(object);
+        while (++index < length) {
+          var source = sources[index];
+          if (source) {
+            assigner(object, source, index, customizer);
+          }
+        }
+        return object;
+      });
+    }
+
+    /**
+     * Creates a `baseEach` or `baseEachRight` function.
+     *
+     * @private
+     * @param {Function} eachFunc The function to iterate over a collection.
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseEach(eachFunc, fromRight) {
+      return function(collection, iteratee) {
+        if (collection == null) {
+          return collection;
+        }
+        if (!isArrayLike(collection)) {
+          return eachFunc(collection, iteratee);
+        }
+        var length = collection.length,
+            index = fromRight ? length : -1,
+            iterable = Object(collection);
+
+        while ((fromRight ? index-- : ++index < length)) {
+          if (iteratee(iterable[index], index, iterable) === false) {
+            break;
+          }
+        }
+        return collection;
+      };
+    }
+
+    /**
+     * Creates a base function for methods like `_.forIn` and `_.forOwn`.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new base function.
+     */
+    function createBaseFor(fromRight) {
+      return function(object, iteratee, keysFunc) {
+        var index = -1,
+            iterable = Object(object),
+            props = keysFunc(object),
+            length = props.length;
+
+        while (length--) {
+          var key = props[fromRight ? length : ++index];
+          if (iteratee(iterable[key], key, iterable) === false) {
+            break;
+          }
+        }
+        return object;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the optional `this`
+     * binding of `thisArg`.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createBaseWrapper(func, bitmask, thisArg) {
+      var isBind = bitmask & BIND_FLAG,
+          Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return fn.apply(isBind ? thisArg : this, arguments);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.lowerFirst`.
+     *
+     * @private
+     * @param {string} methodName The name of the `String` case method to use.
+     * @returns {Function} Returns the new function.
+     */
+    function createCaseFirst(methodName) {
+      return function(string) {
+        string = toString(string);
+
+        var strSymbols = reHasComplexSymbol.test(string)
+          ? stringToArray(string)
+          : undefined;
+
+        var chr = strSymbols
+          ? strSymbols[0]
+          : string.charAt(0);
+
+        var trailing = strSymbols
+          ? castSlice(strSymbols, 1).join('')
+          : string.slice(1);
+
+        return chr[methodName]() + trailing;
+      };
+    }
+
+    /**
+     * Creates a function like `_.camelCase`.
+     *
+     * @private
+     * @param {Function} callback The function to combine each word.
+     * @returns {Function} Returns the new compounder function.
+     */
+    function createCompounder(callback) {
+      return function(string) {
+        return arrayReduce(words(deburr(string).replace(reApos, '')), callback, '');
+      };
+    }
+
+    /**
+     * Creates a function that produces an instance of `Ctor` regardless of
+     * whether it was invoked as part of a `new` expression or by `call` or `apply`.
+     *
+     * @private
+     * @param {Function} Ctor The constructor to wrap.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCtorWrapper(Ctor) {
+      return function() {
+        // Use a `switch` statement to work with class constructors. See
+        // http://ecma-international.org/ecma-262/6.0/#sec-ecmascript-function-objects-call-thisargument-argumentslist
+        // for more details.
+        var args = arguments;
+        switch (args.length) {
+          case 0: return new Ctor;
+          case 1: return new Ctor(args[0]);
+          case 2: return new Ctor(args[0], args[1]);
+          case 3: return new Ctor(args[0], args[1], args[2]);
+          case 4: return new Ctor(args[0], args[1], args[2], args[3]);
+          case 5: return new Ctor(args[0], args[1], args[2], args[3], args[4]);
+          case 6: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5]);
+          case 7: return new Ctor(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
+        }
+        var thisBinding = baseCreate(Ctor.prototype),
+            result = Ctor.apply(thisBinding, args);
+
+        // Mimic the constructor's `return` behavior.
+        // See https://es5.github.io/#x13.2.2 for more details.
+        return isObject(result) ? result : thisBinding;
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to enable currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {number} arity The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createCurryWrapper(func, bitmask, arity) {
+      var Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            args = Array(length),
+            index = length,
+            placeholder = getPlaceholder(wrapper);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        var holders = (length < 3 && args[0] !== placeholder && args[length - 1] !== placeholder)
+          ? []
+          : replaceHolders(args, placeholder);
+
+        length -= holders.length;
+        if (length < arity) {
+          return createRecurryWrapper(
+            func, bitmask, createHybridWrapper, wrapper.placeholder, undefined,
+            args, holders, undefined, undefined, arity - length);
+        }
+        var fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+        return apply(fn, this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.flow` or `_.flowRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new flow function.
+     */
+    function createFlow(fromRight) {
+      return rest(function(funcs) {
+        funcs = baseFlatten(funcs, 1);
+
+        var length = funcs.length,
+            index = length,
+            prereq = LodashWrapper.prototype.thru;
+
+        if (fromRight) {
+          funcs.reverse();
+        }
+        while (index--) {
+          var func = funcs[index];
+          if (typeof func != 'function') {
+            throw new TypeError(FUNC_ERROR_TEXT);
+          }
+          if (prereq && !wrapper && getFuncName(func) == 'wrapper') {
+            var wrapper = new LodashWrapper([], true);
+          }
+        }
+        index = wrapper ? index : length;
+        while (++index < length) {
+          func = funcs[index];
+
+          var funcName = getFuncName(func),
+              data = funcName == 'wrapper' ? getData(func) : undefined;
+
+          if (data && isLaziable(data[0]) &&
+                data[1] == (ARY_FLAG | CURRY_FLAG | PARTIAL_FLAG | REARG_FLAG) &&
+                !data[4].length && data[9] == 1
+              ) {
+            wrapper = wrapper[getFuncName(data[0])].apply(wrapper, data[3]);
+          } else {
+            wrapper = (func.length == 1 && isLaziable(func))
+              ? wrapper[funcName]()
+              : wrapper.thru(func);
+          }
+        }
+        return function() {
+          var args = arguments,
+              value = args[0];
+
+          if (wrapper && args.length == 1 &&
+              isArray(value) && value.length >= LARGE_ARRAY_SIZE) {
+            return wrapper.plant(value).value();
+          }
+          var index = 0,
+              result = length ? funcs[index].apply(this, args) : value;
+
+          while (++index < length) {
+            result = funcs[index].call(this, result);
+          }
+          return result;
+        };
+      });
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with optional `this`
+     * binding of `thisArg`, partial application, and currying.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [partialsRight] The arguments to append to those provided
+     *  to the new function.
+     * @param {Array} [holdersRight] The `partialsRight` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createHybridWrapper(func, bitmask, thisArg, partials, holders, partialsRight, holdersRight, argPos, ary, arity) {
+      var isAry = bitmask & ARY_FLAG,
+          isBind = bitmask & BIND_FLAG,
+          isBindKey = bitmask & BIND_KEY_FLAG,
+          isCurried = bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG),
+          isFlip = bitmask & FLIP_FLAG,
+          Ctor = isBindKey ? undefined : createCtorWrapper(func);
+
+      function wrapper() {
+        var length = arguments.length,
+            index = length,
+            args = Array(length);
+
+        while (index--) {
+          args[index] = arguments[index];
+        }
+        if (isCurried) {
+          var placeholder = getPlaceholder(wrapper),
+              holdersCount = countHolders(args, placeholder);
+        }
+        if (partials) {
+          args = composeArgs(args, partials, holders, isCurried);
+        }
+        if (partialsRight) {
+          args = composeArgsRight(args, partialsRight, holdersRight, isCurried);
+        }
+        length -= holdersCount;
+        if (isCurried && length < arity) {
+          var newHolders = replaceHolders(args, placeholder);
+          return createRecurryWrapper(
+            func, bitmask, createHybridWrapper, wrapper.placeholder, thisArg,
+            args, newHolders, argPos, ary, arity - length
+          );
+        }
+        var thisBinding = isBind ? thisArg : this,
+            fn = isBindKey ? thisBinding[func] : func;
+
+        length = args.length;
+        if (argPos) {
+          args = reorder(args, argPos);
+        } else if (isFlip && length > 1) {
+          args.reverse();
+        }
+        if (isAry && ary < length) {
+          args.length = ary;
+        }
+        if (this && this !== root && this instanceof wrapper) {
+          fn = Ctor || createCtorWrapper(fn);
+        }
+        return fn.apply(thisBinding, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a function like `_.invertBy`.
+     *
+     * @private
+     * @param {Function} setter The function to set accumulator values.
+     * @param {Function} toIteratee The function to resolve iteratees.
+     * @returns {Function} Returns the new inverter function.
+     */
+    function createInverter(setter, toIteratee) {
+      return function(object, iteratee) {
+        return baseInverter(object, setter, toIteratee(iteratee), {});
+      };
+    }
+
+    /**
+     * Creates a function that performs a mathematical operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @returns {Function} Returns the new mathematical operation function.
+     */
+    function createMathOperation(operator) {
+      return function(value, other) {
+        var result;
+        if (value === undefined && other === undefined) {
+          return 0;
+        }
+        if (value !== undefined) {
+          result = value;
+        }
+        if (other !== undefined) {
+          if (result === undefined) {
+            return other;
+          }
+          if (typeof value == 'string' || typeof other == 'string') {
+            value = baseToString(value);
+            other = baseToString(other);
+          } else {
+            value = baseToNumber(value);
+            other = baseToNumber(other);
+          }
+          result = operator(value, other);
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function like `_.over`.
+     *
+     * @private
+     * @param {Function} arrayFunc The function to iterate over iteratees.
+     * @returns {Function} Returns the new invoker function.
+     */
+    function createOver(arrayFunc) {
+      return rest(function(iteratees) {
+        iteratees = (iteratees.length == 1 && isArray(iteratees[0]))
+          ? arrayMap(iteratees[0], baseUnary(getIteratee()))
+          : arrayMap(baseFlatten(iteratees, 1, isFlattenableIteratee), baseUnary(getIteratee()));
+
+        return rest(function(args) {
+          var thisArg = this;
+          return arrayFunc(iteratees, function(iteratee) {
+            return apply(iteratee, thisArg, args);
+          });
+        });
+      });
+    }
+
+    /**
+     * Creates the padding for `string` based on `length`. The `chars` string
+     * is truncated if the number of characters exceeds `length`.
+     *
+     * @private
+     * @param {number} length The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padding for `string`.
+     */
+    function createPadding(length, chars) {
+      chars = chars === undefined ? ' ' : baseToString(chars);
+
+      var charsLength = chars.length;
+      if (charsLength < 2) {
+        return charsLength ? baseRepeat(chars, length) : chars;
+      }
+      var result = baseRepeat(chars, nativeCeil(length / stringSize(chars)));
+      return reHasComplexSymbol.test(chars)
+        ? castSlice(stringToArray(result), 0, length).join('')
+        : result.slice(0, length);
+    }
+
+    /**
+     * Creates a function that wraps `func` to invoke it with the `this` binding
+     * of `thisArg` and `partials` prepended to the arguments it receives.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {Array} partials The arguments to prepend to those provided to
+     *  the new function.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createPartialWrapper(func, bitmask, thisArg, partials) {
+      var isBind = bitmask & BIND_FLAG,
+          Ctor = createCtorWrapper(func);
+
+      function wrapper() {
+        var argsIndex = -1,
+            argsLength = arguments.length,
+            leftIndex = -1,
+            leftLength = partials.length,
+            args = Array(leftLength + argsLength),
+            fn = (this && this !== root && this instanceof wrapper) ? Ctor : func;
+
+        while (++leftIndex < leftLength) {
+          args[leftIndex] = partials[leftIndex];
+        }
+        while (argsLength--) {
+          args[leftIndex++] = arguments[++argsIndex];
+        }
+        return apply(fn, isBind ? thisArg : this, args);
+      }
+      return wrapper;
+    }
+
+    /**
+     * Creates a `_.range` or `_.rangeRight` function.
+     *
+     * @private
+     * @param {boolean} [fromRight] Specify iterating from right to left.
+     * @returns {Function} Returns the new range function.
+     */
+    function createRange(fromRight) {
+      return function(start, end, step) {
+        if (step && typeof step != 'number' && isIterateeCall(start, end, step)) {
+          end = step = undefined;
+        }
+        // Ensure the sign of `-0` is preserved.
+        start = toNumber(start);
+        start = start === start ? start : 0;
+        if (end === undefined) {
+          end = start;
+          start = 0;
+        } else {
+          end = toNumber(end) || 0;
+        }
+        step = step === undefined ? (start < end ? 1 : -1) : (toNumber(step) || 0);
+        return baseRange(start, end, step, fromRight);
+      };
+    }
+
+    /**
+     * Creates a function that performs a relational operation on two values.
+     *
+     * @private
+     * @param {Function} operator The function to perform the operation.
+     * @returns {Function} Returns the new relational operation function.
+     */
+    function createRelationalOperation(operator) {
+      return function(value, other) {
+        if (!(typeof value == 'string' && typeof other == 'string')) {
+          value = toNumber(value);
+          other = toNumber(other);
+        }
+        return operator(value, other);
+      };
+    }
+
+    /**
+     * Creates a function that wraps `func` to continue currying.
+     *
+     * @private
+     * @param {Function} func The function to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags. See `createWrapper`
+     *  for more details.
+     * @param {Function} wrapFunc The function to create the `func` wrapper.
+     * @param {*} placeholder The placeholder value.
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to prepend to those provided to
+     *  the new function.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createRecurryWrapper(func, bitmask, wrapFunc, placeholder, thisArg, partials, holders, argPos, ary, arity) {
+      var isCurry = bitmask & CURRY_FLAG,
+          newHolders = isCurry ? holders : undefined,
+          newHoldersRight = isCurry ? undefined : holders,
+          newPartials = isCurry ? partials : undefined,
+          newPartialsRight = isCurry ? undefined : partials;
+
+      bitmask |= (isCurry ? PARTIAL_FLAG : PARTIAL_RIGHT_FLAG);
+      bitmask &= ~(isCurry ? PARTIAL_RIGHT_FLAG : PARTIAL_FLAG);
+
+      if (!(bitmask & CURRY_BOUND_FLAG)) {
+        bitmask &= ~(BIND_FLAG | BIND_KEY_FLAG);
+      }
+      var newData = [
+        func, bitmask, thisArg, newPartials, newHolders, newPartialsRight,
+        newHoldersRight, argPos, ary, arity
+      ];
+
+      var result = wrapFunc.apply(undefined, newData);
+      if (isLaziable(func)) {
+        setData(result, newData);
+      }
+      result.placeholder = placeholder;
+      return result;
+    }
+
+    /**
+     * Creates a function like `_.round`.
+     *
+     * @private
+     * @param {string} methodName The name of the `Math` method to use when rounding.
+     * @returns {Function} Returns the new round function.
+     */
+    function createRound(methodName) {
+      var func = Math[methodName];
+      return function(number, precision) {
+        number = toNumber(number);
+        precision = toInteger(precision);
+        if (precision) {
+          // Shift with exponential notation to avoid floating-point issues.
+          // See [MDN](https://mdn.io/round#Examples) for more details.
+          var pair = (toString(number) + 'e').split('e'),
+              value = func(pair[0] + 'e' + (+pair[1] + precision));
+
+          pair = (toString(value) + 'e').split('e');
+          return +(pair[0] + 'e' + (+pair[1] - precision));
+        }
+        return func(number);
+      };
+    }
+
+    /**
+     * Creates a set of `values`.
+     *
+     * @private
+     * @param {Array} values The values to add to the set.
+     * @returns {Object} Returns the new set.
+     */
+    var createSet = !(Set && (1 / setToArray(new Set([,-0]))[1]) == INFINITY) ? noop : function(values) {
+      return new Set(values);
+    };
+
+    /**
+     * Creates a function that either curries or invokes `func` with optional
+     * `this` binding and partially applied arguments.
+     *
+     * @private
+     * @param {Function|string} func The function or method name to wrap.
+     * @param {number} bitmask The bitmask of wrapper flags.
+     *  The bitmask may be composed of the following flags:
+     *     1 - `_.bind`
+     *     2 - `_.bindKey`
+     *     4 - `_.curry` or `_.curryRight` of a bound function
+     *     8 - `_.curry`
+     *    16 - `_.curryRight`
+     *    32 - `_.partial`
+     *    64 - `_.partialRight`
+     *   128 - `_.rearg`
+     *   256 - `_.ary`
+     * @param {*} [thisArg] The `this` binding of `func`.
+     * @param {Array} [partials] The arguments to be partially applied.
+     * @param {Array} [holders] The `partials` placeholder indexes.
+     * @param {Array} [argPos] The argument positions of the new function.
+     * @param {number} [ary] The arity cap of `func`.
+     * @param {number} [arity] The arity of `func`.
+     * @returns {Function} Returns the new wrapped function.
+     */
+    function createWrapper(func, bitmask, thisArg, partials, holders, argPos, ary, arity) {
+      var isBindKey = bitmask & BIND_KEY_FLAG;
+      if (!isBindKey && typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var length = partials ? partials.length : 0;
+      if (!length) {
+        bitmask &= ~(PARTIAL_FLAG | PARTIAL_RIGHT_FLAG);
+        partials = holders = undefined;
+      }
+      ary = ary === undefined ? ary : nativeMax(toInteger(ary), 0);
+      arity = arity === undefined ? arity : toInteger(arity);
+      length -= holders ? holders.length : 0;
+
+      if (bitmask & PARTIAL_RIGHT_FLAG) {
+        var partialsRight = partials,
+            holdersRight = holders;
+
+        partials = holders = undefined;
+      }
+      var data = isBindKey ? undefined : getData(func);
+
+      var newData = [
+        func, bitmask, thisArg, partials, holders, partialsRight, holdersRight,
+        argPos, ary, arity
+      ];
+
+      if (data) {
+        mergeData(newData, data);
+      }
+      func = newData[0];
+      bitmask = newData[1];
+      thisArg = newData[2];
+      partials = newData[3];
+      holders = newData[4];
+      arity = newData[9] = newData[9] == null
+        ? (isBindKey ? 0 : func.length)
+        : nativeMax(newData[9] - length, 0);
+
+      if (!arity && bitmask & (CURRY_FLAG | CURRY_RIGHT_FLAG)) {
+        bitmask &= ~(CURRY_FLAG | CURRY_RIGHT_FLAG);
+      }
+      if (!bitmask || bitmask == BIND_FLAG) {
+        var result = createBaseWrapper(func, bitmask, thisArg);
+      } else if (bitmask == CURRY_FLAG || bitmask == CURRY_RIGHT_FLAG) {
+        result = createCurryWrapper(func, bitmask, arity);
+      } else if ((bitmask == PARTIAL_FLAG || bitmask == (BIND_FLAG | PARTIAL_FLAG)) && !holders.length) {
+        result = createPartialWrapper(func, bitmask, thisArg, partials);
+      } else {
+        result = createHybridWrapper.apply(undefined, newData);
+      }
+      var setter = data ? baseSetData : setData;
+      return setter(result, newData);
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for arrays with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Array} array The array to compare.
+     * @param {Array} other The other array to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} stack Tracks traversed `array` and `other` objects.
+     * @returns {boolean} Returns `true` if the arrays are equivalent, else `false`.
+     */
+    function equalArrays(array, other, equalFunc, customizer, bitmask, stack) {
+      var index = -1,
+          isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+          isUnordered = bitmask & UNORDERED_COMPARE_FLAG,
+          arrLength = array.length,
+          othLength = other.length;
+
+      if (arrLength != othLength && !(isPartial && othLength > arrLength)) {
+        return false;
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(array);
+      if (stacked) {
+        return stacked == other;
+      }
+      var result = true;
+      stack.set(array, other);
+
+      // Ignore non-index properties.
+      while (++index < arrLength) {
+        var arrValue = array[index],
+            othValue = other[index];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, arrValue, index, other, array, stack)
+            : customizer(arrValue, othValue, index, array, other, stack);
+        }
+        if (compared !== undefined) {
+          if (compared) {
+            continue;
+          }
+          result = false;
+          break;
+        }
+        // Recursively compare arrays (susceptible to call stack limits).
+        if (isUnordered) {
+          if (!arraySome(other, function(othValue) {
+                return arrValue === othValue ||
+                  equalFunc(arrValue, othValue, customizer, bitmask, stack);
+              })) {
+            result = false;
+            break;
+          }
+        } else if (!(
+              arrValue === othValue ||
+                equalFunc(arrValue, othValue, customizer, bitmask, stack)
+            )) {
+          result = false;
+          break;
+        }
+      }
+      stack['delete'](array);
+      return result;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for comparing objects of
+     * the same `toStringTag`.
+     *
+     * **Note:** This function only supports comparing values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {string} tag The `toStringTag` of the objects to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalByTag(object, other, tag, equalFunc, customizer, bitmask, stack) {
+      switch (tag) {
+        case dataViewTag:
+          if ((object.byteLength != other.byteLength) ||
+              (object.byteOffset != other.byteOffset)) {
+            return false;
+          }
+          object = object.buffer;
+          other = other.buffer;
+
+        case arrayBufferTag:
+          if ((object.byteLength != other.byteLength) ||
+              !equalFunc(new Uint8Array(object), new Uint8Array(other))) {
+            return false;
+          }
+          return true;
+
+        case boolTag:
+        case dateTag:
+          // 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 +object == +other;
+
+        case errorTag:
+          return object.name == other.name && object.message == other.message;
+
+        case numberTag:
+          // Treat `NaN` vs. `NaN` as equal.
+          return (object != +object) ? other != +other : object == +other;
+
+        case regexpTag:
+        case stringTag:
+          // Coerce regexes to strings and treat strings, primitives and objects,
+          // as equal. See http://www.ecma-international.org/ecma-262/6.0/#sec-regexp.prototype.tostring
+          // for more details.
+          return object == (other + '');
+
+        case mapTag:
+          var convert = mapToArray;
+
+        case setTag:
+          var isPartial = bitmask & PARTIAL_COMPARE_FLAG;
+          convert || (convert = setToArray);
+
+          if (object.size != other.size && !isPartial) {
+            return false;
+          }
+          // Assume cyclic values are equal.
+          var stacked = stack.get(object);
+          if (stacked) {
+            return stacked == other;
+          }
+          bitmask |= UNORDERED_COMPARE_FLAG;
+          stack.set(object, other);
+
+          // Recursively compare objects (susceptible to call stack limits).
+          return equalArrays(convert(object), convert(other), equalFunc, customizer, bitmask, stack);
+
+        case symbolTag:
+          if (symbolValueOf) {
+            return symbolValueOf.call(object) == symbolValueOf.call(other);
+          }
+      }
+      return false;
+    }
+
+    /**
+     * A specialized version of `baseIsEqualDeep` for objects with support for
+     * partial deep comparisons.
+     *
+     * @private
+     * @param {Object} object The object to compare.
+     * @param {Object} other The other object to compare.
+     * @param {Function} equalFunc The function to determine equivalents of values.
+     * @param {Function} customizer The function to customize comparisons.
+     * @param {number} bitmask The bitmask of comparison flags. See `baseIsEqual`
+     *  for more details.
+     * @param {Object} stack Tracks traversed `object` and `other` objects.
+     * @returns {boolean} Returns `true` if the objects are equivalent, else `false`.
+     */
+    function equalObjects(object, other, equalFunc, customizer, bitmask, stack) {
+      var isPartial = bitmask & PARTIAL_COMPARE_FLAG,
+          objProps = keys(object),
+          objLength = objProps.length,
+          othProps = keys(other),
+          othLength = othProps.length;
+
+      if (objLength != othLength && !isPartial) {
+        return false;
+      }
+      var index = objLength;
+      while (index--) {
+        var key = objProps[index];
+        if (!(isPartial ? key in other : baseHas(other, key))) {
+          return false;
+        }
+      }
+      // Assume cyclic values are equal.
+      var stacked = stack.get(object);
+      if (stacked) {
+        return stacked == other;
+      }
+      var result = true;
+      stack.set(object, other);
+
+      var skipCtor = isPartial;
+      while (++index < objLength) {
+        key = objProps[index];
+        var objValue = object[key],
+            othValue = other[key];
+
+        if (customizer) {
+          var compared = isPartial
+            ? customizer(othValue, objValue, key, other, object, stack)
+            : customizer(objValue, othValue, key, object, other, stack);
+        }
+        // Recursively compare objects (susceptible to call stack limits).
+        if (!(compared === undefined
+              ? (objValue === othValue || equalFunc(objValue, othValue, customizer, bitmask, stack))
+              : compared
+            )) {
+          result = false;
+          break;
+        }
+        skipCtor || (skipCtor = key == 'constructor');
+      }
+      if (result && !skipCtor) {
+        var objCtor = object.constructor,
+            othCtor = other.constructor;
+
+        // Non `Object` object instances with different constructors are not equal.
+        if (objCtor != othCtor &&
+            ('constructor' in object && 'constructor' in other) &&
+            !(typeof objCtor == 'function' && objCtor instanceof objCtor &&
+              typeof othCtor == 'function' && othCtor instanceof othCtor)) {
+          result = false;
+        }
+      }
+      stack['delete'](object);
+      return result;
+    }
+
+    /**
+     * Creates an array of own enumerable property names and symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeys(object) {
+      return baseGetAllKeys(object, keys, getSymbols);
+    }
+
+    /**
+     * Creates an array of own and inherited enumerable property names and
+     * symbols of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names and symbols.
+     */
+    function getAllKeysIn(object) {
+      return baseGetAllKeys(object, keysIn, getSymbolsIn);
+    }
+
+    /**
+     * Gets metadata for `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {*} Returns the metadata for `func`.
+     */
+    var getData = !metaMap ? noop : function(func) {
+      return metaMap.get(func);
+    };
+
+    /**
+     * Gets the name of `func`.
+     *
+     * @private
+     * @param {Function} func The function to query.
+     * @returns {string} Returns the function name.
+     */
+    function getFuncName(func) {
+      var result = (func.name + ''),
+          array = realNames[result],
+          length = hasOwnProperty.call(realNames, result) ? array.length : 0;
+
+      while (length--) {
+        var data = array[length],
+            otherFunc = data.func;
+        if (otherFunc == null || otherFunc == func) {
+          return data.name;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Gets the appropriate "iteratee" function. If `_.iteratee` is customized,
+     * this function returns the custom method, otherwise it returns `baseIteratee`.
+     * If arguments are provided, the chosen function is invoked with them and
+     * its result is returned.
+     *
+     * @private
+     * @param {*} [value] The value to convert to an iteratee.
+     * @param {number} [arity] The arity of the created iteratee.
+     * @returns {Function} Returns the chosen function or its result.
+     */
+    function getIteratee() {
+      var result = lodash.iteratee || iteratee;
+      result = result === iteratee ? baseIteratee : result;
+      return arguments.length ? result(arguments[0], arguments[1]) : result;
+    }
+
+    /**
+     * Gets the "length" property value of `object`.
+     *
+     * **Note:** This function is used to avoid a
+     * [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) that affects
+     * Safari on at least iOS 8.1-8.3 ARM64.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {*} Returns the "length" value.
+     */
+    var getLength = baseProperty('length');
+
+    /**
+     * Gets the property names, values, and compare flags of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the match data of `object`.
+     */
+    function getMatchData(object) {
+      var result = toPairs(object),
+          length = result.length;
+
+      while (length--) {
+        result[length][2] = isStrictComparable(result[length][1]);
+      }
+      return result;
+    }
+
+    /**
+     * Gets the native function at `key` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {string} key The key of the method to get.
+     * @returns {*} Returns the function if it's native, else `undefined`.
+     */
+    function getNative(object, key) {
+      var value = object[key];
+      return isNative(value) ? value : undefined;
+    }
+
+    /**
+     * Gets the argument placeholder value for `func`.
+     *
+     * @private
+     * @param {Function} func The function to inspect.
+     * @returns {*} Returns the placeholder value.
+     */
+    function getPlaceholder(func) {
+      var object = hasOwnProperty.call(lodash, 'placeholder') ? lodash : func;
+      return object.placeholder;
+    }
+
+    /**
+     * Gets the `[[Prototype]]` of `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {null|Object} Returns the `[[Prototype]]`.
+     */
+    function getPrototype(value) {
+      return nativeGetPrototype(Object(value));
+    }
+
+    /**
+     * Creates an array of the own enumerable symbol properties of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    function getSymbols(object) {
+      // Coerce `object` to an object to avoid non-object errors in V8.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=3443 for more details.
+      return getOwnPropertySymbols(Object(object));
+    }
+
+    // Fallback for IE < 11.
+    if (!getOwnPropertySymbols) {
+      getSymbols = function() {
+        return [];
+      };
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable symbol properties
+     * of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of symbols.
+     */
+    var getSymbolsIn = !getOwnPropertySymbols ? getSymbols : function(object) {
+      var result = [];
+      while (object) {
+        arrayPush(result, getSymbols(object));
+        object = getPrototype(object);
+      }
+      return result;
+    };
+
+    /**
+     * Gets the `toStringTag` of `value`.
+     *
+     * @private
+     * @param {*} value The value to query.
+     * @returns {string} Returns the `toStringTag`.
+     */
+    function getTag(value) {
+      return objectToString.call(value);
+    }
+
+    // Fallback for data views, maps, sets, and weak maps in IE 11,
+    // for data views in Edge, and promises in Node.js.
+    if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) ||
+        (Map && getTag(new Map) != mapTag) ||
+        (Promise && getTag(Promise.resolve()) != promiseTag) ||
+        (Set && getTag(new Set) != setTag) ||
+        (WeakMap && getTag(new WeakMap) != weakMapTag)) {
+      getTag = function(value) {
+        var result = objectToString.call(value),
+            Ctor = result == objectTag ? value.constructor : undefined,
+            ctorString = Ctor ? toSource(Ctor) : undefined;
+
+        if (ctorString) {
+          switch (ctorString) {
+            case dataViewCtorString: return dataViewTag;
+            case mapCtorString: return mapTag;
+            case promiseCtorString: return promiseTag;
+            case setCtorString: return setTag;
+            case weakMapCtorString: return weakMapTag;
+          }
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Gets the view, applying any `transforms` to the `start` and `end` positions.
+     *
+     * @private
+     * @param {number} start The start of the view.
+     * @param {number} end The end of the view.
+     * @param {Array} transforms The transformations to apply to the view.
+     * @returns {Object} Returns an object containing the `start` and `end`
+     *  positions of the view.
+     */
+    function getView(start, end, transforms) {
+      var index = -1,
+          length = transforms.length;
+
+      while (++index < length) {
+        var data = transforms[index],
+            size = data.size;
+
+        switch (data.type) {
+          case 'drop':      start += size; break;
+          case 'dropRight': end -= size; break;
+          case 'take':      end = nativeMin(end, start + size); break;
+          case 'takeRight': start = nativeMax(start, end - size); break;
+        }
+      }
+      return { 'start': start, 'end': end };
+    }
+
+    /**
+     * Checks if `path` exists on `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @param {Function} hasFunc The function to check properties.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     */
+    function hasPath(object, path, hasFunc) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var result,
+          index = -1,
+          length = path.length;
+
+      while (++index < length) {
+        var key = toKey(path[index]);
+        if (!(result = object != null && hasFunc(object, key))) {
+          break;
+        }
+        object = object[key];
+      }
+      if (result) {
+        return result;
+      }
+      var length = object ? object.length : 0;
+      return !!length && isLength(length) && isIndex(key, length) &&
+        (isArray(object) || isString(object) || isArguments(object));
+    }
+
+    /**
+     * Initializes an array clone.
+     *
+     * @private
+     * @param {Array} array The array to clone.
+     * @returns {Array} Returns the initialized clone.
+     */
+    function initCloneArray(array) {
+      var length = array.length,
+          result = array.constructor(length);
+
+      // Add properties assigned by `RegExp#exec`.
+      if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) {
+        result.index = array.index;
+        result.input = array.input;
+      }
+      return result;
+    }
+
+    /**
+     * Initializes an object clone.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneObject(object) {
+      return (typeof object.constructor == 'function' && !isPrototype(object))
+        ? baseCreate(getPrototype(object))
+        : {};
+    }
+
+    /**
+     * Initializes an object clone based on its `toStringTag`.
+     *
+     * **Note:** This function only supports cloning values with tags of
+     * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`.
+     *
+     * @private
+     * @param {Object} object The object to clone.
+     * @param {string} tag The `toStringTag` of the object to clone.
+     * @param {Function} cloneFunc The function to clone values.
+     * @param {boolean} [isDeep] Specify a deep clone.
+     * @returns {Object} Returns the initialized clone.
+     */
+    function initCloneByTag(object, tag, cloneFunc, isDeep) {
+      var Ctor = object.constructor;
+      switch (tag) {
+        case arrayBufferTag:
+          return cloneArrayBuffer(object);
+
+        case boolTag:
+        case dateTag:
+          return new Ctor(+object);
+
+        case dataViewTag:
+          return cloneDataView(object, isDeep);
+
+        case float32Tag: case float64Tag:
+        case int8Tag: case int16Tag: case int32Tag:
+        case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag:
+          return cloneTypedArray(object, isDeep);
+
+        case mapTag:
+          return cloneMap(object, isDeep, cloneFunc);
+
+        case numberTag:
+        case stringTag:
+          return new Ctor(object);
+
+        case regexpTag:
+          return cloneRegExp(object);
+
+        case setTag:
+          return cloneSet(object, isDeep, cloneFunc);
+
+        case symbolTag:
+          return cloneSymbol(object);
+      }
+    }
+
+    /**
+     * Creates an array of index keys for `object` values of arrays,
+     * `arguments` objects, and strings, otherwise `null` is returned.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @returns {Array|null} Returns index keys, else `null`.
+     */
+    function indexKeys(object) {
+      var length = object ? object.length : undefined;
+      if (isLength(length) &&
+          (isArray(object) || isString(object) || isArguments(object))) {
+        return baseTimes(length, String);
+      }
+      return null;
+    }
+
+    /**
+     * Checks if `value` is a flattenable `arguments` object or array.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+     */
+    function isFlattenable(value) {
+      return isArrayLikeObject(value) && (isArray(value) || isArguments(value));
+    }
+
+    /**
+     * Checks if `value` is a flattenable array and not a `_.matchesProperty`
+     * iteratee shorthand.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is flattenable, else `false`.
+     */
+    function isFlattenableIteratee(value) {
+      return isArray(value) && !(value.length == 2 && !isFunction(value[0]));
+    }
+
+    /**
+     * Checks if `value` is a valid array-like index.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index.
+     * @returns {boolean} Returns `true` if `value` is a valid index, else `false`.
+     */
+    function isIndex(value, length) {
+      length = length == null ? MAX_SAFE_INTEGER : length;
+      return !!length &&
+        (typeof value == 'number' || reIsUint.test(value)) &&
+        (value > -1 && value % 1 == 0 && value < length);
+    }
+
+    /**
+     * Checks if the given arguments are from an iteratee call.
+     *
+     * @private
+     * @param {*} value The potential iteratee value argument.
+     * @param {*} index The potential iteratee index or key argument.
+     * @param {*} object The potential iteratee object argument.
+     * @returns {boolean} Returns `true` if the arguments are from an iteratee call,
+     *  else `false`.
+     */
+    function isIterateeCall(value, index, object) {
+      if (!isObject(object)) {
+        return false;
+      }
+      var type = typeof index;
+      if (type == 'number'
+            ? (isArrayLike(object) && isIndex(index, object.length))
+            : (type == 'string' && index in object)
+          ) {
+        return eq(object[index], value);
+      }
+      return false;
+    }
+
+    /**
+     * Checks if `value` is a property name and not a property path.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @param {Object} [object] The object to query keys on.
+     * @returns {boolean} Returns `true` if `value` is a property name, else `false`.
+     */
+    function isKey(value, object) {
+      if (isArray(value)) {
+        return false;
+      }
+      var type = typeof value;
+      if (type == 'number' || type == 'symbol' || type == 'boolean' ||
+          value == null || isSymbol(value)) {
+        return true;
+      }
+      return reIsPlainProp.test(value) || !reIsDeepProp.test(value) ||
+        (object != null && value in Object(object));
+    }
+
+    /**
+     * Checks if `value` is suitable for use as unique object key.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is suitable, else `false`.
+     */
+    function isKeyable(value) {
+      var type = typeof value;
+      return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean')
+        ? (value !== '__proto__')
+        : (value === null);
+    }
+
+    /**
+     * Checks if `func` has a lazy counterpart.
+     *
+     * @private
+     * @param {Function} func The function to check.
+     * @returns {boolean} Returns `true` if `func` has a lazy counterpart,
+     *  else `false`.
+     */
+    function isLaziable(func) {
+      var funcName = getFuncName(func),
+          other = lodash[funcName];
+
+      if (typeof other != 'function' || !(funcName in LazyWrapper.prototype)) {
+        return false;
+      }
+      if (func === other) {
+        return true;
+      }
+      var data = getData(other);
+      return !!data && func === data[0];
+    }
+
+    /**
+     * Checks if `value` is likely a prototype object.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a prototype, else `false`.
+     */
+    function isPrototype(value) {
+      var Ctor = value && value.constructor,
+          proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto;
+
+      return value === proto;
+    }
+
+    /**
+     * Checks if `value` is suitable for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` if suitable for strict
+     *  equality comparisons, else `false`.
+     */
+    function isStrictComparable(value) {
+      return value === value && !isObject(value);
+    }
+
+    /**
+     * A specialized version of `matchesProperty` for source values suitable
+     * for strict equality comparisons, i.e. `===`.
+     *
+     * @private
+     * @param {string} key The key of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     */
+    function matchesStrictComparable(key, srcValue) {
+      return function(object) {
+        if (object == null) {
+          return false;
+        }
+        return object[key] === srcValue &&
+          (srcValue !== undefined || (key in Object(object)));
+      };
+    }
+
+    /**
+     * Merges the function metadata of `source` into `data`.
+     *
+     * Merging metadata reduces the number of wrappers used to invoke a function.
+     * This is possible because methods like `_.bind`, `_.curry`, and `_.partial`
+     * may be applied regardless of execution order. Methods like `_.ary` and
+     * `_.rearg` modify function arguments, making the order in which they are
+     * executed important, preventing the merging of metadata. However, we make
+     * an exception for a safe combined case where curried functions have `_.ary`
+     * and or `_.rearg` applied.
+     *
+     * @private
+     * @param {Array} data The destination metadata.
+     * @param {Array} source The source metadata.
+     * @returns {Array} Returns `data`.
+     */
+    function mergeData(data, source) {
+      var bitmask = data[1],
+          srcBitmask = source[1],
+          newBitmask = bitmask | srcBitmask,
+          isCommon = newBitmask < (BIND_FLAG | BIND_KEY_FLAG | ARY_FLAG);
+
+      var isCombo =
+        ((srcBitmask == ARY_FLAG) && (bitmask == CURRY_FLAG)) ||
+        ((srcBitmask == ARY_FLAG) && (bitmask == REARG_FLAG) && (data[7].length <= source[8])) ||
+        ((srcBitmask == (ARY_FLAG | REARG_FLAG)) && (source[7].length <= source[8]) && (bitmask == CURRY_FLAG));
+
+      // Exit early if metadata can't be merged.
+      if (!(isCommon || isCombo)) {
+        return data;
+      }
+      // Use source `thisArg` if available.
+      if (srcBitmask & BIND_FLAG) {
+        data[2] = source[2];
+        // Set when currying a bound function.
+        newBitmask |= bitmask & BIND_FLAG ? 0 : CURRY_BOUND_FLAG;
+      }
+      // Compose partial arguments.
+      var value = source[3];
+      if (value) {
+        var partials = data[3];
+        data[3] = partials ? composeArgs(partials, value, source[4]) : value;
+        data[4] = partials ? replaceHolders(data[3], PLACEHOLDER) : source[4];
+      }
+      // Compose partial right arguments.
+      value = source[5];
+      if (value) {
+        partials = data[5];
+        data[5] = partials ? composeArgsRight(partials, value, source[6]) : value;
+        data[6] = partials ? replaceHolders(data[5], PLACEHOLDER) : source[6];
+      }
+      // Use source `argPos` if available.
+      value = source[7];
+      if (value) {
+        data[7] = value;
+      }
+      // Use source `ary` if it's smaller.
+      if (srcBitmask & ARY_FLAG) {
+        data[8] = data[8] == null ? source[8] : nativeMin(data[8], source[8]);
+      }
+      // Use source `arity` if one is not provided.
+      if (data[9] == null) {
+        data[9] = source[9];
+      }
+      // Use source `func` and merge bitmasks.
+      data[0] = source[0];
+      data[1] = newBitmask;
+
+      return data;
+    }
+
+    /**
+     * Used by `_.defaultsDeep` to customize its `_.merge` use.
+     *
+     * @private
+     * @param {*} objValue The destination value.
+     * @param {*} srcValue The source value.
+     * @param {string} key The key of the property to merge.
+     * @param {Object} object The parent object of `objValue`.
+     * @param {Object} source The parent object of `srcValue`.
+     * @param {Object} [stack] Tracks traversed source values and their merged
+     *  counterparts.
+     * @returns {*} Returns the value to assign.
+     */
+    function mergeDefaults(objValue, srcValue, key, object, source, stack) {
+      if (isObject(objValue) && isObject(srcValue)) {
+        baseMerge(objValue, srcValue, undefined, mergeDefaults, stack.set(srcValue, objValue));
+      }
+      return objValue;
+    }
+
+    /**
+     * Gets the parent value at `path` of `object`.
+     *
+     * @private
+     * @param {Object} object The object to query.
+     * @param {Array} path The path to get the parent value of.
+     * @returns {*} Returns the parent value.
+     */
+    function parent(object, path) {
+      return path.length == 1 ? object : baseGet(object, baseSlice(path, 0, -1));
+    }
+
+    /**
+     * Reorder `array` according to the specified indexes where the element at
+     * the first index is assigned as the first element, the element at
+     * the second index is assigned as the second element, and so on.
+     *
+     * @private
+     * @param {Array} array The array to reorder.
+     * @param {Array} indexes The arranged array indexes.
+     * @returns {Array} Returns `array`.
+     */
+    function reorder(array, indexes) {
+      var arrLength = array.length,
+          length = nativeMin(indexes.length, arrLength),
+          oldArray = copyArray(array);
+
+      while (length--) {
+        var index = indexes[length];
+        array[length] = isIndex(index, arrLength) ? oldArray[index] : undefined;
+      }
+      return array;
+    }
+
+    /**
+     * Sets metadata for `func`.
+     *
+     * **Note:** If this function becomes hot, i.e. is invoked a lot in a short
+     * period of time, it will trip its breaker and transition to an identity
+     * function to avoid garbage collection pauses in V8. See
+     * [V8 issue 2070](https://bugs.chromium.org/p/v8/issues/detail?id=2070)
+     * for more details.
+     *
+     * @private
+     * @param {Function} func The function to associate metadata with.
+     * @param {*} data The metadata.
+     * @returns {Function} Returns `func`.
+     */
+    var setData = (function() {
+      var count = 0,
+          lastCalled = 0;
+
+      return function(key, value) {
+        var stamp = now(),
+            remaining = HOT_SPAN - (stamp - lastCalled);
+
+        lastCalled = stamp;
+        if (remaining > 0) {
+          if (++count >= HOT_COUNT) {
+            return key;
+          }
+        } else {
+          count = 0;
+        }
+        return baseSetData(key, value);
+      };
+    }());
+
+    /**
+     * Converts `string` to a property path array.
+     *
+     * @private
+     * @param {string} string The string to convert.
+     * @returns {Array} Returns the property path array.
+     */
+    var stringToPath = memoize(function(string) {
+      var result = [];
+      toString(string).replace(rePropName, function(match, number, quote, string) {
+        result.push(quote ? string.replace(reEscapeChar, '$1') : (number || match));
+      });
+      return result;
+    });
+
+    /**
+     * Converts `value` to a string key if it's not a string or symbol.
+     *
+     * @private
+     * @param {*} value The value to inspect.
+     * @returns {string|symbol} Returns the key.
+     */
+    function toKey(value) {
+      if (typeof value == 'string' || isSymbol(value)) {
+        return value;
+      }
+      var result = (value + '');
+      return (result == '0' && (1 / value) == -INFINITY) ? '-0' : result;
+    }
+
+    /**
+     * Converts `func` to its source code.
+     *
+     * @private
+     * @param {Function} func The function to process.
+     * @returns {string} Returns the source code.
+     */
+    function toSource(func) {
+      if (func != null) {
+        try {
+          return funcToString.call(func);
+        } catch (e) {}
+        try {
+          return (func + '');
+        } catch (e) {}
+      }
+      return '';
+    }
+
+    /**
+     * Creates a clone of `wrapper`.
+     *
+     * @private
+     * @param {Object} wrapper The wrapper to clone.
+     * @returns {Object} Returns the cloned wrapper.
+     */
+    function wrapperClone(wrapper) {
+      if (wrapper instanceof LazyWrapper) {
+        return wrapper.clone();
+      }
+      var result = new LodashWrapper(wrapper.__wrapped__, wrapper.__chain__);
+      result.__actions__ = copyArray(wrapper.__actions__);
+      result.__index__  = wrapper.__index__;
+      result.__values__ = wrapper.__values__;
+      return result;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an array of elements split into groups the length of `size`.
+     * If `array` can't be split evenly, the final chunk will be the remaining
+     * elements.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to process.
+     * @param {number} [size=1] The length of each chunk
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the new array containing chunks.
+     * @example
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 2);
+     * // => [['a', 'b'], ['c', 'd']]
+     *
+     * _.chunk(['a', 'b', 'c', 'd'], 3);
+     * // => [['a', 'b', 'c'], ['d']]
+     */
+    function chunk(array, size, guard) {
+      if ((guard ? isIterateeCall(array, size, guard) : size === undefined)) {
+        size = 1;
+      } else {
+        size = nativeMax(toInteger(size), 0);
+      }
+      var length = array ? array.length : 0;
+      if (!length || size < 1) {
+        return [];
+      }
+      var index = 0,
+          resIndex = 0,
+          result = Array(nativeCeil(length / size));
+
+      while (index < length) {
+        result[resIndex++] = baseSlice(array, index, (index += size));
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array with all falsey values removed. The values `false`, `null`,
+     * `0`, `""`, `undefined`, and `NaN` are falsey.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to compact.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.compact([0, 1, false, 2, '', 3]);
+     * // => [1, 2, 3]
+     */
+    function compact(array) {
+      var index = -1,
+          length = array ? array.length : 0,
+          resIndex = 0,
+          result = [];
+
+      while (++index < length) {
+        var value = array[index];
+        if (value) {
+          result[resIndex++] = value;
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates a new array concatenating `array` with any additional arrays
+     * and/or values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to concatenate.
+     * @param {...*} [values] The values to concatenate.
+     * @returns {Array} Returns the new concatenated array.
+     * @example
+     *
+     * var array = [1];
+     * var other = _.concat(array, 2, [3], [[4]]);
+     *
+     * console.log(other);
+     * // => [1, 2, 3, [4]]
+     *
+     * console.log(array);
+     * // => [1]
+     */
+    function concat() {
+      var length = arguments.length,
+          array = castArray(arguments[0]);
+
+      if (length < 2) {
+        return length ? copyArray(array) : [];
+      }
+      var args = Array(length - 1);
+      while (length--) {
+        args[length - 1] = arguments[length];
+      }
+      return arrayConcat(array, baseFlatten(args, 1));
+    }
+
+    /**
+     * Creates an array of unique `array` values not included in the other given
+     * arrays using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. The order of result values is determined by the
+     * order they occur in the first array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.without, _.xor
+     * @example
+     *
+     * _.difference([3, 2, 1], [4, 2]);
+     * // => [3, 1]
+     */
+    var difference = rest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `iteratee` which
+     * is invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. Result values are chosen from the first array.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
+     * // => [3.1, 1.3]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var differenceBy = rest(function(array, values) {
+      var iteratee = last(values);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), getIteratee(iteratee))
+        : [];
+    });
+
+    /**
+     * This method is like `_.difference` except that it accepts `comparator`
+     * which is invoked to compare elements of `array` to `values`. Result values
+     * are chosen from the first array. The comparator is invoked with two arguments:
+     * (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {...Array} [values] The values to exclude.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of filtered values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     *
+     * _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }]
+     */
+    var differenceWith = rest(function(array, values) {
+      var comparator = last(values);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return isArrayLikeObject(array)
+        ? baseDifference(array, baseFlatten(values, 1, isArrayLikeObject, true), undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.drop([1, 2, 3]);
+     * // => [2, 3]
+     *
+     * _.drop([1, 2, 3], 2);
+     * // => [3]
+     *
+     * _.drop([1, 2, 3], 5);
+     * // => []
+     *
+     * _.drop([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function drop(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements dropped from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to drop.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.dropRight([1, 2, 3]);
+     * // => [1, 2]
+     *
+     * _.dropRight([1, 2, 3], 2);
+     * // => [1]
+     *
+     * _.dropRight([1, 2, 3], 5);
+     * // => []
+     *
+     * _.dropRight([1, 2, 3], 0);
+     * // => [1, 2, 3]
+     */
+    function dropRight(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the end.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.dropRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropRightWhile(users, ['active', false]);
+     * // => objects for ['barney']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropRightWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` excluding elements dropped from the beginning.
+     * Elements are dropped until `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.dropWhile(users, function(o) { return !o.active; });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.dropWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.dropWhile(users, ['active', false]);
+     * // => objects for ['pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.dropWhile(users, 'active');
+     * // => objects for ['barney', 'fred', 'pebbles']
+     */
+    function dropWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), true)
+        : [];
+    }
+
+    /**
+     * Fills elements of `array` with `value` from `start` up to, but not
+     * including, `end`.
+     *
+     * **Note:** This method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Array
+     * @param {Array} array The array to fill.
+     * @param {*} value The value to fill `array` with.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.fill(array, 'a');
+     * console.log(array);
+     * // => ['a', 'a', 'a']
+     *
+     * _.fill(Array(3), 2);
+     * // => [2, 2, 2]
+     *
+     * _.fill([4, 6, 8, 10], '*', 1, 3);
+     * // => [4, '*', '*', 10]
+     */
+    function fill(array, value, start, end) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (start && typeof start != 'number' && isIterateeCall(array, value, start)) {
+        start = 0;
+        end = length;
+      }
+      return baseFill(array, value, start, end);
+    }
+
+    /**
+     * This method is like `_.find` except that it returns the index of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.findIndex(users, function(o) { return o.user == 'barney'; });
+     * // => 0
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findIndex(users, { 'user': 'fred', 'active': false });
+     * // => 1
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findIndex(users, ['active', false]);
+     * // => 0
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findIndex(users, 'active');
+     * // => 2
+     */
+    function findIndex(array, predicate) {
+      return (array && array.length)
+        ? baseFindIndex(array, getIteratee(predicate, 3))
+        : -1;
+    }
+
+    /**
+     * This method is like `_.findIndex` except that it iterates over elements
+     * of `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {number} Returns the index of the found element, else `-1`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.findLastIndex(users, function(o) { return o.user == 'pebbles'; });
+     * // => 2
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastIndex(users, { 'user': 'barney', 'active': true });
+     * // => 0
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastIndex(users, ['active', false]);
+     * // => 2
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastIndex(users, 'active');
+     * // => 0
+     */
+    function findLastIndex(array, predicate) {
+      return (array && array.length)
+        ? baseFindIndex(array, getIteratee(predicate, 3), true)
+        : -1;
+    }
+
+    /**
+     * Flattens `array` a single level deep.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flatten([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, [3, [4]], 5]
+     */
+    function flatten(array) {
+      var length = array ? array.length : 0;
+      return length ? baseFlatten(array, 1) : [];
+    }
+
+    /**
+     * Recursively flattens `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * _.flattenDeep([1, [2, [3, [4]], 5]]);
+     * // => [1, 2, 3, 4, 5]
+     */
+    function flattenDeep(array) {
+      var length = array ? array.length : 0;
+      return length ? baseFlatten(array, INFINITY) : [];
+    }
+
+    /**
+     * Recursively flatten `array` up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Array
+     * @param {Array} array The array to flatten.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * var array = [1, [2, [3, [4]], 5]];
+     *
+     * _.flattenDepth(array, 1);
+     * // => [1, 2, [3, [4]], 5]
+     *
+     * _.flattenDepth(array, 2);
+     * // => [1, 2, 3, [4], 5]
+     */
+    function flattenDepth(array, depth) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(array, depth);
+    }
+
+    /**
+     * The inverse of `_.toPairs`; this method returns an object composed
+     * from key-value `pairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} pairs The key-value pairs.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.fromPairs([['fred', 30], ['barney', 40]]);
+     * // => { 'fred': 30, 'barney': 40 }
+     */
+    function fromPairs(pairs) {
+      var index = -1,
+          length = pairs ? pairs.length : 0,
+          result = {};
+
+      while (++index < length) {
+        var pair = pairs[index];
+        result[pair[0]] = pair[1];
+      }
+      return result;
+    }
+
+    /**
+     * Gets the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias first
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the first element of `array`.
+     * @example
+     *
+     * _.head([1, 2, 3]);
+     * // => 1
+     *
+     * _.head([]);
+     * // => undefined
+     */
+    function head(array) {
+      return (array && array.length) ? array[0] : undefined;
+    }
+
+    /**
+     * Gets the index at which the first occurrence of `value` is found in `array`
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. If `fromIndex` is negative, it's used as the
+     * offset from the end of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.indexOf([1, 2, 1, 2], 2);
+     * // => 1
+     *
+     * // Search from the `fromIndex`.
+     * _.indexOf([1, 2, 1, 2], 2, 2);
+     * // => 3
+     */
+    function indexOf(array, value, fromIndex) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return -1;
+      }
+      fromIndex = toInteger(fromIndex);
+      if (fromIndex < 0) {
+        fromIndex = nativeMax(length + fromIndex, 0);
+      }
+      return baseIndexOf(array, value, fromIndex);
+    }
+
+    /**
+     * Gets all but the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.initial([1, 2, 3]);
+     * // => [1, 2]
+     */
+    function initial(array) {
+      return dropRight(array, 1);
+    }
+
+    /**
+     * Creates an array of unique values that are included in all given arrays
+     * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons. The order of result values is determined by the
+     * order they occur in the first array.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersection([2, 1], [4, 2], [1, 2]);
+     * // => [2]
+     */
+    var intersection = rest(function(arrays) {
+      var mapped = arrayMap(arrays, castArrayLikeObject);
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped)
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `iteratee`
+     * which is invoked for each element of each `arrays` to generate the criterion
+     * by which they're compared. Result values are chosen from the first array.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * _.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+     * // => [2.1]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }]
+     */
+    var intersectionBy = rest(function(arrays) {
+      var iteratee = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      if (iteratee === last(mapped)) {
+        iteratee = undefined;
+      } else {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, getIteratee(iteratee))
+        : [];
+    });
+
+    /**
+     * This method is like `_.intersection` except that it accepts `comparator`
+     * which is invoked to compare elements of `arrays`. Result values are chosen
+     * from the first array. The comparator is invoked with two arguments:
+     * (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of intersecting values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.intersectionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }]
+     */
+    var intersectionWith = rest(function(arrays) {
+      var comparator = last(arrays),
+          mapped = arrayMap(arrays, castArrayLikeObject);
+
+      if (comparator === last(mapped)) {
+        comparator = undefined;
+      } else {
+        mapped.pop();
+      }
+      return (mapped.length && mapped[0] === arrays[0])
+        ? baseIntersection(mapped, undefined, comparator)
+        : [];
+    });
+
+    /**
+     * Converts all elements in `array` into a string separated by `separator`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to convert.
+     * @param {string} [separator=','] The element separator.
+     * @returns {string} Returns the joined string.
+     * @example
+     *
+     * _.join(['a', 'b', 'c'], '~');
+     * // => 'a~b~c'
+     */
+    function join(array, separator) {
+      return array ? nativeJoin.call(array, separator) : '';
+    }
+
+    /**
+     * Gets the last element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {*} Returns the last element of `array`.
+     * @example
+     *
+     * _.last([1, 2, 3]);
+     * // => 3
+     */
+    function last(array) {
+      var length = array ? array.length : 0;
+      return length ? array[length - 1] : undefined;
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it iterates over elements of
+     * `array` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} 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, else `-1`.
+     * @example
+     *
+     * _.lastIndexOf([1, 2, 1, 2], 2);
+     * // => 3
+     *
+     * // Search from the `fromIndex`.
+     * _.lastIndexOf([1, 2, 1, 2], 2, 2);
+     * // => 1
+     */
+    function lastIndexOf(array, value, fromIndex) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return -1;
+      }
+      var index = length;
+      if (fromIndex !== undefined) {
+        index = toInteger(fromIndex);
+        index = (
+          index < 0
+            ? nativeMax(length + index, 0)
+            : nativeMin(index, length - 1)
+        ) + 1;
+      }
+      if (value !== value) {
+        return indexOfNaN(array, index, true);
+      }
+      while (index--) {
+        if (array[index] === value) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * Gets the nth element of `array`. If `n` is negative, the nth element
+     * from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.11.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=0] The index of the element to return.
+     * @returns {*} Returns the nth element of `array`.
+     * @example
+     *
+     * var array = ['a', 'b', 'c', 'd'];
+     *
+     * _.nth(array, 1);
+     * // => 'b'
+     *
+     * _.nth(array, -2);
+     * // => 'c';
+     */
+    function nth(array, n) {
+      return (array && array.length) ? baseNth(array, toInteger(n)) : undefined;
+    }
+
+    /**
+     * Removes all given values from `array` using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * **Note:** Unlike `_.without`, this method mutates `array`. Use `_.remove`
+     * to remove elements from an array by predicate.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...*} [values] The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     *
+     * _.pull(array, 2, 3);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    var pull = rest(pullAll);
+
+    /**
+     * This method is like `_.pull` except that it accepts an array of values to remove.
+     *
+     * **Note:** Unlike `_.difference`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3, 1, 2, 3];
+     *
+     * _.pullAll(array, [2, 3]);
+     * console.log(array);
+     * // => [1, 1]
+     */
+    function pullAll(array, values) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values)
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `iteratee` which is
+     * invoked for each element of `array` and `values` to generate the criterion
+     * by which they're compared. The iteratee is invoked with one argument: (value).
+     *
+     * **Note:** Unlike `_.differenceBy`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+     *
+     * _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], 'x');
+     * console.log(array);
+     * // => [{ 'x': 2 }]
+     */
+    function pullAllBy(array, values, iteratee) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, getIteratee(iteratee))
+        : array;
+    }
+
+    /**
+     * This method is like `_.pullAll` except that it accepts `comparator` which
+     * is invoked to compare elements of `array` to `values`. The comparator is
+     * invoked with two arguments: (arrVal, othVal).
+     *
+     * **Note:** Unlike `_.differenceWith`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array} values The values to remove.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [{ 'x': 1, 'y': 2 }, { 'x': 3, 'y': 4 }, { 'x': 5, 'y': 6 }];
+     *
+     * _.pullAllWith(array, [{ 'x': 3, 'y': 4 }], _.isEqual);
+     * console.log(array);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 5, 'y': 6 }]
+     */
+    function pullAllWith(array, values, comparator) {
+      return (array && array.length && values && values.length)
+        ? basePullAll(array, values, undefined, comparator)
+        : array;
+    }
+
+    /**
+     * Removes elements from `array` corresponding to `indexes` and returns an
+     * array of removed elements.
+     *
+     * **Note:** Unlike `_.at`, this method mutates `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {...(number|number[])} [indexes] The indexes of elements to remove.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [5, 10, 15, 20];
+     * var evens = _.pullAt(array, 1, 3);
+     *
+     * console.log(array);
+     * // => [5, 15]
+     *
+     * console.log(evens);
+     * // => [10, 20]
+     */
+    var pullAt = rest(function(array, indexes) {
+      indexes = baseFlatten(indexes, 1);
+
+      var length = array ? array.length : 0,
+          result = baseAt(array, indexes);
+
+      basePullAt(array, arrayMap(indexes, function(index) {
+        return isIndex(index, length) ? +index : index;
+      }).sort(compareAscending));
+
+      return result;
+    });
+
+    /**
+     * Removes all elements from `array` that `predicate` returns truthy for
+     * and returns an array of the removed elements. The predicate is invoked
+     * with three arguments: (value, index, array).
+     *
+     * **Note:** Unlike `_.filter`, this method mutates `array`. Use `_.pull`
+     * to pull elements from an array by value.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new array of removed elements.
+     * @example
+     *
+     * var array = [1, 2, 3, 4];
+     * var evens = _.remove(array, function(n) {
+     *   return n % 2 == 0;
+     * });
+     *
+     * console.log(array);
+     * // => [1, 3]
+     *
+     * console.log(evens);
+     * // => [2, 4]
+     */
+    function remove(array, predicate) {
+      var result = [];
+      if (!(array && array.length)) {
+        return result;
+      }
+      var index = -1,
+          indexes = [],
+          length = array.length;
+
+      predicate = getIteratee(predicate, 3);
+      while (++index < length) {
+        var value = array[index];
+        if (predicate(value, index, array)) {
+          result.push(value);
+          indexes.push(index);
+        }
+      }
+      basePullAt(array, indexes);
+      return result;
+    }
+
+    /**
+     * Reverses `array` so that the first element becomes the last, the second
+     * element becomes the second to last, and so on.
+     *
+     * **Note:** This method mutates `array` and is based on
+     * [`Array#reverse`](https://mdn.io/Array/reverse).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to modify.
+     * @returns {Array} Returns `array`.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _.reverse(array);
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function reverse(array) {
+      return array ? nativeReverse.call(array) : array;
+    }
+
+    /**
+     * Creates a slice of `array` from `start` up to, but not including, `end`.
+     *
+     * **Note:** This method is used instead of
+     * [`Array#slice`](https://mdn.io/Array/slice) to ensure dense arrays are
+     * returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to slice.
+     * @param {number} [start=0] The start position.
+     * @param {number} [end=array.length] The end position.
+     * @returns {Array} Returns the slice of `array`.
+     */
+    function slice(array, start, end) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      if (end && typeof end != 'number' && isIterateeCall(array, start, end)) {
+        start = 0;
+        end = length;
+      }
+      else {
+        start = start == null ? 0 : toInteger(start);
+        end = end === undefined ? length : toInteger(end);
+      }
+      return baseSlice(array, start, end);
+    }
+
+    /**
+     * Uses a binary search to determine the lowest index at which `value`
+     * should be inserted into `array` in order to maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedIndex([30, 50], 40);
+     * // => 1
+     *
+     * _.sortedIndex([4, 5], 4);
+     * // => 0
+     */
+    function sortedIndex(array, value) {
+      return baseSortedIndex(array, value);
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * var dict = { 'thirty': 30, 'forty': 40, 'fifty': 50 };
+     *
+     * _.sortedIndexBy(['thirty', 'fifty'], 'forty', _.propertyOf(dict));
+     * // => 1
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+     * // => 0
+     */
+    function sortedIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee));
+    }
+
+    /**
+     * This method is like `_.indexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedIndexOf([1, 1, 2, 2], 2);
+     * // => 2
+     */
+    function sortedIndexOf(array, value) {
+      var length = array ? array.length : 0;
+      if (length) {
+        var index = baseSortedIndex(array, value);
+        if (index < length && eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.sortedIndex` except that it returns the highest
+     * index at which `value` should be inserted into `array` in order to
+     * maintain its sort order.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * _.sortedLastIndex([4, 5], 4);
+     * // => 1
+     */
+    function sortedLastIndex(array, value) {
+      return baseSortedIndex(array, value, true);
+    }
+
+    /**
+     * This method is like `_.sortedLastIndex` except that it accepts `iteratee`
+     * which is invoked for `value` and each element of `array` to compute their
+     * sort ranking. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The sorted array to inspect.
+     * @param {*} value The value to evaluate.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the index at which `value` should be inserted
+     *  into `array`.
+     * @example
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sortedLastIndexBy([{ 'x': 4 }, { 'x': 5 }], { 'x': 4 }, 'x');
+     * // => 1
+     */
+    function sortedLastIndexBy(array, value, iteratee) {
+      return baseSortedIndexBy(array, value, getIteratee(iteratee), true);
+    }
+
+    /**
+     * This method is like `_.lastIndexOf` except that it performs a binary
+     * search on a sorted `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to search.
+     * @param {*} value The value to search for.
+     * @returns {number} Returns the index of the matched value, else `-1`.
+     * @example
+     *
+     * _.sortedLastIndexOf([1, 1, 2, 2], 2);
+     * // => 3
+     */
+    function sortedLastIndexOf(array, value) {
+      var length = array ? array.length : 0;
+      if (length) {
+        var index = baseSortedIndex(array, value, true) - 1;
+        if (eq(array[index], value)) {
+          return index;
+        }
+      }
+      return -1;
+    }
+
+    /**
+     * This method is like `_.uniq` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniq([1, 1, 2]);
+     * // => [1, 2]
+     */
+    function sortedUniq(array) {
+      return (array && array.length)
+        ? baseSortedUniq(array)
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniqBy` except that it's designed and optimized
+     * for sorted arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [iteratee] The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.sortedUniqBy([1.1, 1.2, 2.3, 2.4], Math.floor);
+     * // => [1.1, 2.3]
+     */
+    function sortedUniqBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSortedUniq(array, getIteratee(iteratee))
+        : [];
+    }
+
+    /**
+     * Gets all but the first element of `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.tail([1, 2, 3]);
+     * // => [2, 3]
+     */
+    function tail(array) {
+      return drop(array, 1);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the beginning.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.take([1, 2, 3]);
+     * // => [1]
+     *
+     * _.take([1, 2, 3], 2);
+     * // => [1, 2]
+     *
+     * _.take([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.take([1, 2, 3], 0);
+     * // => []
+     */
+    function take(array, n, guard) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      return baseSlice(array, 0, n < 0 ? 0 : n);
+    }
+
+    /**
+     * Creates a slice of `array` with `n` elements taken from the end.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {number} [n=1] The number of elements to take.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * _.takeRight([1, 2, 3]);
+     * // => [3]
+     *
+     * _.takeRight([1, 2, 3], 2);
+     * // => [2, 3]
+     *
+     * _.takeRight([1, 2, 3], 5);
+     * // => [1, 2, 3]
+     *
+     * _.takeRight([1, 2, 3], 0);
+     * // => []
+     */
+    function takeRight(array, n, guard) {
+      var length = array ? array.length : 0;
+      if (!length) {
+        return [];
+      }
+      n = (guard || n === undefined) ? 1 : toInteger(n);
+      n = length - n;
+      return baseSlice(array, n < 0 ? 0 : n, length);
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the end. Elements are
+     * taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': true },
+     *   { 'user': 'fred',    'active': false },
+     *   { 'user': 'pebbles', 'active': false }
+     * ];
+     *
+     * _.takeRightWhile(users, function(o) { return !o.active; });
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeRightWhile(users, { 'user': 'pebbles', 'active': false });
+     * // => objects for ['pebbles']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeRightWhile(users, ['active', false]);
+     * // => objects for ['fred', 'pebbles']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeRightWhile(users, 'active');
+     * // => []
+     */
+    function takeRightWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3), false, true)
+        : [];
+    }
+
+    /**
+     * Creates a slice of `array` with elements taken from the beginning. Elements
+     * are taken until `predicate` returns falsey. The predicate is invoked with
+     * three arguments: (value, index, array).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Array
+     * @param {Array} array The array to query.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the slice of `array`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'active': false },
+     *   { 'user': 'fred',    'active': false},
+     *   { 'user': 'pebbles', 'active': true }
+     * ];
+     *
+     * _.takeWhile(users, function(o) { return !o.active; });
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.takeWhile(users, { 'user': 'barney', 'active': false });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.takeWhile(users, ['active', false]);
+     * // => objects for ['barney', 'fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.takeWhile(users, 'active');
+     * // => []
+     */
+    function takeWhile(array, predicate) {
+      return (array && array.length)
+        ? baseWhile(array, getIteratee(predicate, 3))
+        : [];
+    }
+
+    /**
+     * Creates an array of unique values, in order, from all given arrays using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.union([2, 1], [4, 2], [1, 2]);
+     * // => [2, 1, 4]
+     */
+    var union = rest(function(arrays) {
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which uniqueness is computed. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * _.unionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+     * // => [2.1, 1.2, 4.3]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    var unionBy = rest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), getIteratee(iteratee));
+    });
+
+    /**
+     * This method is like `_.union` except that it accepts `comparator` which
+     * is invoked to compare elements of `arrays`. The comparator is invoked
+     * with two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of combined values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.unionWith(objects, others, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var unionWith = rest(function(arrays) {
+      var comparator = last(arrays);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return baseUniq(baseFlatten(arrays, 1, isArrayLikeObject, true), undefined, comparator);
+    });
+
+    /**
+     * Creates a duplicate-free version of an array, using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons, in which only the first occurrence of each
+     * element is kept.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniq([2, 1, 2]);
+     * // => [2, 1]
+     */
+    function uniq(array) {
+      return (array && array.length)
+        ? baseUniq(array)
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * uniqueness is computed. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * _.uniqBy([2.1, 1.2, 2.3], Math.floor);
+     * // => [2.1, 1.2]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.uniqBy([{ 'x': 1 }, { 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 1 }, { 'x': 2 }]
+     */
+    function uniqBy(array, iteratee) {
+      return (array && array.length)
+        ? baseUniq(array, getIteratee(iteratee))
+        : [];
+    }
+
+    /**
+     * This method is like `_.uniq` except that it accepts `comparator` which
+     * is invoked to compare elements of `array`. The comparator is invoked with
+     * two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {Array} array The array to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new duplicate free array.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 },  { 'x': 1, 'y': 2 }];
+     *
+     * _.uniqWith(objects, _.isEqual);
+     * // => [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }]
+     */
+    function uniqWith(array, comparator) {
+      return (array && array.length)
+        ? baseUniq(array, undefined, comparator)
+        : [];
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts an array of grouped
+     * elements and creates an array regrouping the elements to their pre-zip
+     * configuration.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.2.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     *
+     * _.unzip(zipped);
+     * // => [['fred', 'barney'], [30, 40], [true, false]]
+     */
+    function unzip(array) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var length = 0;
+      array = arrayFilter(array, function(group) {
+        if (isArrayLikeObject(group)) {
+          length = nativeMax(group.length, length);
+          return true;
+        }
+      });
+      return baseTimes(length, function(index) {
+        return arrayMap(array, baseProperty(index));
+      });
+    }
+
+    /**
+     * This method is like `_.unzip` except that it accepts `iteratee` to specify
+     * how regrouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {Array} array The array of grouped elements to process.
+     * @param {Function} [iteratee=_.identity] The function to combine
+     *  regrouped values.
+     * @returns {Array} Returns the new array of regrouped elements.
+     * @example
+     *
+     * var zipped = _.zip([1, 2], [10, 20], [100, 200]);
+     * // => [[1, 10, 100], [2, 20, 200]]
+     *
+     * _.unzipWith(zipped, _.add);
+     * // => [3, 30, 300]
+     */
+    function unzipWith(array, iteratee) {
+      if (!(array && array.length)) {
+        return [];
+      }
+      var result = unzip(array);
+      if (iteratee == null) {
+        return result;
+      }
+      return arrayMap(result, function(group) {
+        return apply(iteratee, undefined, group);
+      });
+    }
+
+    /**
+     * Creates an array excluding all given values using
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * for equality comparisons.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {Array} array The array to filter.
+     * @param {...*} [values] The values to exclude.
+     * @returns {Array} Returns the new array of filtered values.
+     * @see _.difference, _.xor
+     * @example
+     *
+     * _.without([1, 2, 1, 3], 1, 2);
+     * // => [3]
+     */
+    var without = rest(function(array, values) {
+      return isArrayLikeObject(array)
+        ? baseDifference(array, values)
+        : [];
+    });
+
+    /**
+     * Creates an array of unique values that is the
+     * [symmetric difference](https://en.wikipedia.org/wiki/Symmetric_difference)
+     * of the given arrays. The order of result values is determined by the order
+     * they occur in the arrays.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @returns {Array} Returns the new array of values.
+     * @see _.difference, _.without
+     * @example
+     *
+     * _.xor([2, 1], [4, 2]);
+     * // => [1, 4]
+     */
+    var xor = rest(function(arrays) {
+      return baseXor(arrayFilter(arrays, isArrayLikeObject));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `iteratee` which is
+     * invoked for each element of each `arrays` to generate the criterion by
+     * which by which they're compared. The iteratee is invoked with one argument:
+     * (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Array} Returns the new array of values.
+     * @example
+     *
+     * _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+     * // => [1.2, 4.3]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+     * // => [{ 'x': 2 }]
+     */
+    var xorBy = rest(function(arrays) {
+      var iteratee = last(arrays);
+      if (isArrayLikeObject(iteratee)) {
+        iteratee = undefined;
+      }
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), getIteratee(iteratee));
+    });
+
+    /**
+     * This method is like `_.xor` except that it accepts `comparator` which is
+     * invoked to compare elements of `arrays`. The comparator is invoked with
+     * two arguments: (arrVal, othVal).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to inspect.
+     * @param {Function} [comparator] The comparator invoked per element.
+     * @returns {Array} Returns the new array of values.
+     * @example
+     *
+     * var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }];
+     * var others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }];
+     *
+     * _.xorWith(objects, others, _.isEqual);
+     * // => [{ 'x': 2, 'y': 1 }, { 'x': 1, 'y': 1 }]
+     */
+    var xorWith = rest(function(arrays) {
+      var comparator = last(arrays);
+      if (isArrayLikeObject(comparator)) {
+        comparator = undefined;
+      }
+      return baseXor(arrayFilter(arrays, isArrayLikeObject), undefined, comparator);
+    });
+
+    /**
+     * Creates an array of grouped elements, the first of which contains the
+     * first elements of the given arrays, the second of which contains the
+     * second elements of the given arrays, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zip(['fred', 'barney'], [30, 40], [true, false]);
+     * // => [['fred', 30, true], ['barney', 40, false]]
+     */
+    var zip = rest(unzip);
+
+    /**
+     * This method is like `_.fromPairs` except that it accepts two arrays,
+     * one of property identifiers and one of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.4.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObject(['a', 'b'], [1, 2]);
+     * // => { 'a': 1, 'b': 2 }
+     */
+    function zipObject(props, values) {
+      return baseZipObject(props || [], values || [], assignValue);
+    }
+
+    /**
+     * This method is like `_.zipObject` except that it supports property paths.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Array
+     * @param {Array} [props=[]] The property identifiers.
+     * @param {Array} [values=[]] The property values.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * _.zipObjectDeep(['a.b[0].c', 'a.b[1].d'], [1, 2]);
+     * // => { 'a': { 'b': [{ 'c': 1 }, { 'd': 2 }] } }
+     */
+    function zipObjectDeep(props, values) {
+      return baseZipObject(props || [], values || [], baseSet);
+    }
+
+    /**
+     * This method is like `_.zip` except that it accepts `iteratee` to specify
+     * how grouped values should be combined. The iteratee is invoked with the
+     * elements of each group: (...group).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Array
+     * @param {...Array} [arrays] The arrays to process.
+     * @param {Function} [iteratee=_.identity] The function to combine grouped values.
+     * @returns {Array} Returns the new array of grouped elements.
+     * @example
+     *
+     * _.zipWith([1, 2], [10, 20], [100, 200], function(a, b, c) {
+     *   return a + b + c;
+     * });
+     * // => [111, 222]
+     */
+    var zipWith = rest(function(arrays) {
+      var length = arrays.length,
+          iteratee = length > 1 ? arrays[length - 1] : undefined;
+
+      iteratee = typeof iteratee == 'function' ? (arrays.pop(), iteratee) : undefined;
+      return unzipWith(arrays, iteratee);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates a `lodash` wrapper instance that wraps `value` with explicit method
+     * chain sequences enabled. The result of such sequences must be unwrapped
+     * with `_#value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Seq
+     * @param {*} value The value to wrap.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36 },
+     *   { 'user': 'fred',    'age': 40 },
+     *   { 'user': 'pebbles', 'age': 1 }
+     * ];
+     *
+     * var youngest = _
+     *   .chain(users)
+     *   .sortBy('age')
+     *   .map(function(o) {
+     *     return o.user + ' is ' + o.age;
+     *   })
+     *   .head()
+     *   .value();
+     * // => 'pebbles is 1'
+     */
+    function chain(value) {
+      var result = lodash(value);
+      result.__chain__ = true;
+      return result;
+    }
+
+    /**
+     * This method invokes `interceptor` and returns `value`. The interceptor
+     * is invoked with one argument; (value). The purpose of this method is to
+     * "tap into" a method chain sequence in order to modify intermediate results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * _([1, 2, 3])
+     *  .tap(function(array) {
+     *    // Mutate input array.
+     *    array.pop();
+     *  })
+     *  .reverse()
+     *  .value();
+     * // => [2, 1]
+     */
+    function tap(value, interceptor) {
+      interceptor(value);
+      return value;
+    }
+
+    /**
+     * This method is like `_.tap` except that it returns the result of `interceptor`.
+     * The purpose of this method is to "pass thru" values replacing intermediate
+     * results in a method chain sequence.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Seq
+     * @param {*} value The value to provide to `interceptor`.
+     * @param {Function} interceptor The function to invoke.
+     * @returns {*} Returns the result of `interceptor`.
+     * @example
+     *
+     * _('  abc  ')
+     *  .chain()
+     *  .trim()
+     *  .thru(function(value) {
+     *    return [value];
+     *  })
+     *  .value();
+     * // => ['abc']
+     */
+    function thru(value, interceptor) {
+      return interceptor(value);
+    }
+
+    /**
+     * This method is the wrapper version of `_.at`.
+     *
+     * @name at
+     * @memberOf _
+     * @since 1.0.0
+     * @category Seq
+     * @param {...(string|string[])} [paths] The property paths of elements to pick.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _(object).at(['a[0].b.c', 'a[1]']).value();
+     * // => [3, 4]
+     *
+     * _(['a', 'b', 'c']).at(0, 2).value();
+     * // => ['a', 'c']
+     */
+    var wrapperAt = rest(function(paths) {
+      paths = baseFlatten(paths, 1);
+      var length = paths.length,
+          start = length ? paths[0] : 0,
+          value = this.__wrapped__,
+          interceptor = function(object) { return baseAt(object, paths); };
+
+      if (length > 1 || this.__actions__.length ||
+          !(value instanceof LazyWrapper) || !isIndex(start)) {
+        return this.thru(interceptor);
+      }
+      value = value.slice(start, +start + (length ? 1 : 0));
+      value.__actions__.push({
+        'func': thru,
+        'args': [interceptor],
+        'thisArg': undefined
+      });
+      return new LodashWrapper(value, this.__chain__).thru(function(array) {
+        if (length && !array.length) {
+          array.push(undefined);
+        }
+        return array;
+      });
+    });
+
+    /**
+     * Creates a `lodash` wrapper instance with explicit method chain sequences enabled.
+     *
+     * @name chain
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * // A sequence without explicit chaining.
+     * _(users).head();
+     * // => { 'user': 'barney', 'age': 36 }
+     *
+     * // A sequence with explicit chaining.
+     * _(users)
+     *   .chain()
+     *   .head()
+     *   .pick('user')
+     *   .value();
+     * // => { 'user': 'barney' }
+     */
+    function wrapperChain() {
+      return chain(this);
+    }
+
+    /**
+     * Executes the chain sequence and returns the wrapped result.
+     *
+     * @name commit
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2];
+     * var wrapped = _(array).push(3);
+     *
+     * console.log(array);
+     * // => [1, 2]
+     *
+     * wrapped = wrapped.commit();
+     * console.log(array);
+     * // => [1, 2, 3]
+     *
+     * wrapped.last();
+     * // => 3
+     *
+     * console.log(array);
+     * // => [1, 2, 3]
+     */
+    function wrapperCommit() {
+      return new LodashWrapper(this.value(), this.__chain__);
+    }
+
+    /**
+     * Gets the next value on a wrapped object following the
+     * [iterator protocol](https://mdn.io/iteration_protocols#iterator).
+     *
+     * @name next
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the next iterator value.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 1 }
+     *
+     * wrapped.next();
+     * // => { 'done': false, 'value': 2 }
+     *
+     * wrapped.next();
+     * // => { 'done': true, 'value': undefined }
+     */
+    function wrapperNext() {
+      if (this.__values__ === undefined) {
+        this.__values__ = toArray(this.value());
+      }
+      var done = this.__index__ >= this.__values__.length,
+          value = done ? undefined : this.__values__[this.__index__++];
+
+      return { 'done': done, 'value': value };
+    }
+
+    /**
+     * Enables the wrapper to be iterable.
+     *
+     * @name Symbol.iterator
+     * @memberOf _
+     * @since 4.0.0
+     * @category Seq
+     * @returns {Object} Returns the wrapper object.
+     * @example
+     *
+     * var wrapped = _([1, 2]);
+     *
+     * wrapped[Symbol.iterator]() === wrapped;
+     * // => true
+     *
+     * Array.from(wrapped);
+     * // => [1, 2]
+     */
+    function wrapperToIterator() {
+      return this;
+    }
+
+    /**
+     * Creates a clone of the chain sequence planting `value` as the wrapped value.
+     *
+     * @name plant
+     * @memberOf _
+     * @since 3.2.0
+     * @category Seq
+     * @param {*} value The value to plant.
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var wrapped = _([1, 2]).map(square);
+     * var other = wrapped.plant([3, 4]);
+     *
+     * other.value();
+     * // => [9, 16]
+     *
+     * wrapped.value();
+     * // => [1, 4]
+     */
+    function wrapperPlant(value) {
+      var result,
+          parent = this;
+
+      while (parent instanceof baseLodash) {
+        var clone = wrapperClone(parent);
+        clone.__index__ = 0;
+        clone.__values__ = undefined;
+        if (result) {
+          previous.__wrapped__ = clone;
+        } else {
+          result = clone;
+        }
+        var previous = clone;
+        parent = parent.__wrapped__;
+      }
+      previous.__wrapped__ = value;
+      return result;
+    }
+
+    /**
+     * This method is the wrapper version of `_.reverse`.
+     *
+     * **Note:** This method mutates the wrapped array.
+     *
+     * @name reverse
+     * @memberOf _
+     * @since 0.1.0
+     * @category Seq
+     * @returns {Object} Returns the new `lodash` wrapper instance.
+     * @example
+     *
+     * var array = [1, 2, 3];
+     *
+     * _(array).reverse().value()
+     * // => [3, 2, 1]
+     *
+     * console.log(array);
+     * // => [3, 2, 1]
+     */
+    function wrapperReverse() {
+      var value = this.__wrapped__;
+      if (value instanceof LazyWrapper) {
+        var wrapped = value;
+        if (this.__actions__.length) {
+          wrapped = new LazyWrapper(this);
+        }
+        wrapped = wrapped.reverse();
+        wrapped.__actions__.push({
+          'func': thru,
+          'args': [reverse],
+          'thisArg': undefined
+        });
+        return new LodashWrapper(wrapped, this.__chain__);
+      }
+      return this.thru(reverse);
+    }
+
+    /**
+     * Executes the chain sequence to resolve the unwrapped value.
+     *
+     * @name value
+     * @memberOf _
+     * @since 0.1.0
+     * @alias toJSON, valueOf
+     * @category Seq
+     * @returns {*} Returns the resolved unwrapped value.
+     * @example
+     *
+     * _([1, 2, 3]).value();
+     * // => [1, 2, 3]
+     */
+    function wrapperValue() {
+      return baseWrapperValue(this.__wrapped__, this.__actions__);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the number of times the key was returned by `iteratee`. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.countBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': 1, '6': 2 }
+     *
+     * _.countBy(['one', 'two', 'three'], 'length');
+     * // => { '3': 2, '5': 1 }
+     */
+    var countBy = createAggregator(function(result, value, key) {
+      hasOwnProperty.call(result, key) ? ++result[key] : (result[key] = 1);
+    });
+
+    /**
+     * Checks if `predicate` returns truthy for **all** elements of `collection`.
+     * Iteration is stopped once `predicate` returns falsey. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if all elements pass the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.every([true, 1, null, 'yes'], Boolean);
+     * // => false
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.every(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.every(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.every(users, 'active');
+     * // => false
+     */
+    function every(collection, predicate, guard) {
+      var func = isArray(collection) ? arrayEvery : baseEvery;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning an array of all elements
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.reject
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, { 'age': 36, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.filter(users, 'active');
+     * // => objects for ['barney']
+     */
+    function filter(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Iterates over elements of `collection`, returning the first element
+     * `predicate` returns truthy for. The predicate is invoked with three
+     * arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': true },
+     *   { 'user': 'fred',    'age': 40, 'active': false },
+     *   { 'user': 'pebbles', 'age': 1,  'active': true }
+     * ];
+     *
+     * _.find(users, function(o) { return o.age < 40; });
+     * // => object for 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.find(users, { 'age': 1, 'active': true });
+     * // => object for 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.find(users, ['active', false]);
+     * // => object for 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.find(users, 'active');
+     * // => object for 'barney'
+     */
+    function find(collection, predicate) {
+      predicate = getIteratee(predicate, 3);
+      if (isArray(collection)) {
+        var index = baseFindIndex(collection, predicate);
+        return index > -1 ? collection[index] : undefined;
+      }
+      return baseFind(collection, predicate, baseEach);
+    }
+
+    /**
+     * This method is like `_.find` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {*} Returns the matched element, else `undefined`.
+     * @example
+     *
+     * _.findLast([1, 2, 3, 4], function(n) {
+     *   return n % 2 == 1;
+     * });
+     * // => 3
+     */
+    function findLast(collection, predicate) {
+      predicate = getIteratee(predicate, 3);
+      if (isArray(collection)) {
+        var index = baseFindIndex(collection, predicate, true);
+        return index > -1 ? collection[index] : undefined;
+      }
+      return baseFind(collection, predicate, baseEachRight);
+    }
+
+    /**
+     * Creates a flattened array of values by running each element in `collection`
+     * thru `iteratee` and flattening the mapped results. The iteratee is invoked
+     * with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [n, n];
+     * }
+     *
+     * _.flatMap([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMap(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), 1);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDeep([1, 2], duplicate);
+     * // => [1, 1, 2, 2]
+     */
+    function flatMapDeep(collection, iteratee) {
+      return baseFlatten(map(collection, iteratee), INFINITY);
+    }
+
+    /**
+     * This method is like `_.flatMap` except that it recursively flattens the
+     * mapped results up to `depth` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @param {number} [depth=1] The maximum recursion depth.
+     * @returns {Array} Returns the new flattened array.
+     * @example
+     *
+     * function duplicate(n) {
+     *   return [[[n, n]]];
+     * }
+     *
+     * _.flatMapDepth([1, 2], duplicate, 2);
+     * // => [[1, 1], [2, 2]]
+     */
+    function flatMapDepth(collection, iteratee, depth) {
+      depth = depth === undefined ? 1 : toInteger(depth);
+      return baseFlatten(map(collection, iteratee), depth);
+    }
+
+    /**
+     * Iterates over elements of `collection` and invokes `iteratee` for each element.
+     * The iteratee is invoked with three arguments: (value, index|key, collection).
+     * Iteratee functions may exit iteration early by explicitly returning `false`.
+     *
+     * **Note:** As with other "Collections" methods, objects with a "length"
+     * property are iterated like arrays. To avoid this behavior use `_.forIn`
+     * or `_.forOwn` for object iteration.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @alias each
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEachRight
+     * @example
+     *
+     * _([1, 2]).forEach(function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `1` then `2`.
+     *
+     * _.forEach({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forEach(collection, iteratee) {
+      return (typeof iteratee == 'function' && isArray(collection))
+        ? arrayEach(collection, iteratee)
+        : baseEach(collection, getIteratee(iteratee));
+    }
+
+    /**
+     * This method is like `_.forEach` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @alias eachRight
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array|Object} Returns `collection`.
+     * @see _.forEach
+     * @example
+     *
+     * _.forEachRight([1, 2], function(value) {
+     *   console.log(value);
+     * });
+     * // => Logs `2` then `1`.
+     */
+    function forEachRight(collection, iteratee) {
+      return (typeof iteratee == 'function' && isArray(collection))
+        ? arrayEachRight(collection, iteratee)
+        : baseEachRight(collection, getIteratee(iteratee));
+    }
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The order of grouped values
+     * is determined by the order they occur in `collection`. The corresponding
+     * value of each key is an array of elements responsible for generating the
+     * key. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * _.groupBy([6.1, 4.2, 6.3], Math.floor);
+     * // => { '4': [4.2], '6': [6.1, 6.3] }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.groupBy(['one', 'two', 'three'], 'length');
+     * // => { '3': ['one', 'two'], '5': ['three'] }
+     */
+    var groupBy = createAggregator(function(result, value, key) {
+      if (hasOwnProperty.call(result, key)) {
+        result[key].push(value);
+      } else {
+        result[key] = [value];
+      }
+    });
+
+    /**
+     * Checks if `value` is in `collection`. If `collection` is a string, it's
+     * checked for a substring of `value`, otherwise
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * is used for equality comparisons. If `fromIndex` is negative, it's used as
+     * the offset from the end of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object|string} collection The collection to search.
+     * @param {*} value The value to search for.
+     * @param {number} [fromIndex=0] The index to search from.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {boolean} Returns `true` if `value` is found, else `false`.
+     * @example
+     *
+     * _.includes([1, 2, 3], 1);
+     * // => true
+     *
+     * _.includes([1, 2, 3], 1, 2);
+     * // => false
+     *
+     * _.includes({ 'user': 'fred', 'age': 40 }, 'fred');
+     * // => true
+     *
+     * _.includes('pebbles', 'eb');
+     * // => true
+     */
+    function includes(collection, value, fromIndex, guard) {
+      collection = isArrayLike(collection) ? collection : values(collection);
+      fromIndex = (fromIndex && !guard) ? toInteger(fromIndex) : 0;
+
+      var length = collection.length;
+      if (fromIndex < 0) {
+        fromIndex = nativeMax(length + fromIndex, 0);
+      }
+      return isString(collection)
+        ? (fromIndex <= length && collection.indexOf(value, fromIndex) > -1)
+        : (!!length && baseIndexOf(collection, value, fromIndex) > -1);
+    }
+
+    /**
+     * Invokes the method at `path` of each element in `collection`, returning
+     * an array of the results of each invoked method. Any additional arguments
+     * are provided to each invoked method. If `methodName` is a function, it's
+     * invoked for and `this` bound to, each element in `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|string} path The path of the method to invoke or
+     *  the function invoked per iteration.
+     * @param {...*} [args] The arguments to invoke each method with.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.invokeMap([[5, 1, 7], [3, 2, 1]], 'sort');
+     * // => [[1, 5, 7], [1, 2, 3]]
+     *
+     * _.invokeMap([123, 456], String.prototype.split, '');
+     * // => [['1', '2', '3'], ['4', '5', '6']]
+     */
+    var invokeMap = rest(function(collection, path, args) {
+      var index = -1,
+          isFunc = typeof path == 'function',
+          isProp = isKey(path),
+          result = isArrayLike(collection) ? Array(collection.length) : [];
+
+      baseEach(collection, function(value) {
+        var func = isFunc ? path : ((isProp && value != null) ? value[path] : undefined);
+        result[++index] = func ? apply(func, value, args) : baseInvoke(value, path, args);
+      });
+      return result;
+    });
+
+    /**
+     * Creates an object composed of keys generated from the results of running
+     * each element of `collection` thru `iteratee`. The corresponding value of
+     * each key is the last element responsible for generating the key. The
+     * iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee to transform keys.
+     * @returns {Object} Returns the composed aggregate object.
+     * @example
+     *
+     * var array = [
+     *   { 'dir': 'left', 'code': 97 },
+     *   { 'dir': 'right', 'code': 100 }
+     * ];
+     *
+     * _.keyBy(array, function(o) {
+     *   return String.fromCharCode(o.code);
+     * });
+     * // => { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } }
+     *
+     * _.keyBy(array, 'dir');
+     * // => { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } }
+     */
+    var keyBy = createAggregator(function(result, value, key) {
+      result[key] = value;
+    });
+
+    /**
+     * Creates an array of values by running each element in `collection` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.every`, `_.filter`, `_.map`, `_.mapValues`, `_.reject`, and `_.some`.
+     *
+     * The guarded methods are:
+     * `ary`, `chunk`, `curry`, `curryRight`, `drop`, `dropRight`, `every`,
+     * `fill`, `invert`, `parseInt`, `random`, `range`, `rangeRight`, `repeat`,
+     * `sampleSize`, `slice`, `some`, `sortBy`, `split`, `take`, `takeRight`,
+     * `template`, `trim`, `trimEnd`, `trimStart`, and `words`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new mapped array.
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * _.map([4, 8], square);
+     * // => [16, 64]
+     *
+     * _.map({ 'a': 4, 'b': 8 }, square);
+     * // => [16, 64] (iteration order is not guaranteed)
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, 'user');
+     * // => ['barney', 'fred']
+     */
+    function map(collection, iteratee) {
+      var func = isArray(collection) ? arrayMap : baseMap;
+      return func(collection, getIteratee(iteratee, 3));
+    }
+
+    /**
+     * This method is like `_.sortBy` except that it allows specifying the sort
+     * orders of the iteratees to sort by. If `orders` is unspecified, all values
+     * are sorted in ascending order. Otherwise, specify an order of "desc" for
+     * descending or "asc" for ascending sort order of corresponding values.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array[]|Function[]|Object[]|string[]} [iteratees=[_.identity]]
+     *  The iteratees to sort by.
+     * @param {string[]} [orders] The sort orders of `iteratees`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.reduce`.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 34 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 36 }
+     * ];
+     *
+     * // Sort by `user` in ascending order and by `age` in descending order.
+     * _.orderBy(users, ['user', 'age'], ['asc', 'desc']);
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     */
+    function orderBy(collection, iteratees, orders, guard) {
+      if (collection == null) {
+        return [];
+      }
+      if (!isArray(iteratees)) {
+        iteratees = iteratees == null ? [] : [iteratees];
+      }
+      orders = guard ? undefined : orders;
+      if (!isArray(orders)) {
+        orders = orders == null ? [] : [orders];
+      }
+      return baseOrderBy(collection, iteratees, orders);
+    }
+
+    /**
+     * Creates an array of elements split into two groups, the first of which
+     * contains elements `predicate` returns truthy for, the second of which
+     * contains elements `predicate` returns falsey for. The predicate is
+     * invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the array of grouped elements.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney',  'age': 36, 'active': false },
+     *   { 'user': 'fred',    'age': 40, 'active': true },
+     *   { 'user': 'pebbles', 'age': 1,  'active': false }
+     * ];
+     *
+     * _.partition(users, function(o) { return o.active; });
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.partition(users, { 'age': 1, 'active': false });
+     * // => objects for [['pebbles'], ['barney', 'fred']]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.partition(users, ['active', false]);
+     * // => objects for [['barney', 'pebbles'], ['fred']]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.partition(users, 'active');
+     * // => objects for [['fred'], ['barney', 'pebbles']]
+     */
+    var partition = createAggregator(function(result, value, key) {
+      result[key ? 0 : 1].push(value);
+    }, function() { return [[], []]; });
+
+    /**
+     * Reduces `collection` to a value which is the accumulated result of running
+     * each element in `collection` thru `iteratee`, where each successive
+     * invocation is supplied the return value of the previous. If `accumulator`
+     * is not given, the first element of `collection` is used as the initial
+     * value. The iteratee is invoked with four arguments:
+     * (accumulator, value, index|key, collection).
+     *
+     * Many lodash methods are guarded to work as iteratees for methods like
+     * `_.reduce`, `_.reduceRight`, and `_.transform`.
+     *
+     * The guarded methods are:
+     * `assign`, `defaults`, `defaultsDeep`, `includes`, `merge`, `orderBy`,
+     * and `sortBy`
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduceRight
+     * @example
+     *
+     * _.reduce([1, 2], function(sum, n) {
+     *   return sum + n;
+     * }, 0);
+     * // => 3
+     *
+     * _.reduce({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     *   return result;
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] } (iteration order is not guaranteed)
+     */
+    function reduce(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduce : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEach);
+    }
+
+    /**
+     * This method is like `_.reduce` except that it iterates over elements of
+     * `collection` from right to left.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The initial value.
+     * @returns {*} Returns the accumulated value.
+     * @see _.reduce
+     * @example
+     *
+     * var array = [[0, 1], [2, 3], [4, 5]];
+     *
+     * _.reduceRight(array, function(flattened, other) {
+     *   return flattened.concat(other);
+     * }, []);
+     * // => [4, 5, 2, 3, 0, 1]
+     */
+    function reduceRight(collection, iteratee, accumulator) {
+      var func = isArray(collection) ? arrayReduceRight : baseReduce,
+          initAccum = arguments.length < 3;
+
+      return func(collection, getIteratee(iteratee, 4), accumulator, initAccum, baseEachRight);
+    }
+
+    /**
+     * The opposite of `_.filter`; this method returns the elements of `collection`
+     * that `predicate` does **not** return truthy for.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Array} Returns the new filtered array.
+     * @see _.filter
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': false },
+     *   { 'user': 'fred',   'age': 40, 'active': true }
+     * ];
+     *
+     * _.reject(users, function(o) { return !o.active; });
+     * // => objects for ['fred']
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.reject(users, { 'age': 40, 'active': true });
+     * // => objects for ['barney']
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.reject(users, ['active', false]);
+     * // => objects for ['fred']
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.reject(users, 'active');
+     * // => objects for ['barney']
+     */
+    function reject(collection, predicate) {
+      var func = isArray(collection) ? arrayFilter : baseFilter;
+      predicate = getIteratee(predicate, 3);
+      return func(collection, function(value, index, collection) {
+        return !predicate(value, index, collection);
+      });
+    }
+
+    /**
+     * Gets a random element from `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @returns {*} Returns the random element.
+     * @example
+     *
+     * _.sample([1, 2, 3, 4]);
+     * // => 2
+     */
+    function sample(collection) {
+      var array = isArrayLike(collection) ? collection : values(collection),
+          length = array.length;
+
+      return length > 0 ? array[baseRandom(0, length - 1)] : undefined;
+    }
+
+    /**
+     * Gets `n` random elements at unique keys from `collection` up to the
+     * size of `collection`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to sample.
+     * @param {number} [n=1] The number of elements to sample.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the random elements.
+     * @example
+     *
+     * _.sampleSize([1, 2, 3], 2);
+     * // => [3, 1]
+     *
+     * _.sampleSize([1, 2, 3], 4);
+     * // => [2, 3, 1]
+     */
+    function sampleSize(collection, n, guard) {
+      var index = -1,
+          result = toArray(collection),
+          length = result.length,
+          lastIndex = length - 1;
+
+      if ((guard ? isIterateeCall(collection, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = baseClamp(toInteger(n), 0, length);
+      }
+      while (++index < n) {
+        var rand = baseRandom(index, lastIndex),
+            value = result[rand];
+
+        result[rand] = result[index];
+        result[index] = value;
+      }
+      result.length = n;
+      return result;
+    }
+
+    /**
+     * Creates an array of shuffled values, using a version of the
+     * [Fisher-Yates shuffle](https://en.wikipedia.org/wiki/Fisher-Yates_shuffle).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to shuffle.
+     * @returns {Array} Returns the new shuffled array.
+     * @example
+     *
+     * _.shuffle([1, 2, 3, 4]);
+     * // => [4, 1, 3, 2]
+     */
+    function shuffle(collection) {
+      return sampleSize(collection, MAX_ARRAY_LENGTH);
+    }
+
+    /**
+     * Gets the size of `collection` by returning its length for array-like
+     * values or the number of own enumerable string keyed properties for objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to inspect.
+     * @returns {number} Returns the collection size.
+     * @example
+     *
+     * _.size([1, 2, 3]);
+     * // => 3
+     *
+     * _.size({ 'a': 1, 'b': 2 });
+     * // => 2
+     *
+     * _.size('pebbles');
+     * // => 7
+     */
+    function size(collection) {
+      if (collection == null) {
+        return 0;
+      }
+      if (isArrayLike(collection)) {
+        var result = collection.length;
+        return (result && isString(collection)) ? stringSize(collection) : result;
+      }
+      if (isObjectLike(collection)) {
+        var tag = getTag(collection);
+        if (tag == mapTag || tag == setTag) {
+          return collection.size;
+        }
+      }
+      return keys(collection).length;
+    }
+
+    /**
+     * Checks if `predicate` returns truthy for **any** element of `collection`.
+     * Iteration is stopped once `predicate` returns truthy. The predicate is
+     * invoked with three arguments: (value, index|key, collection).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {boolean} Returns `true` if any element passes the predicate check,
+     *  else `false`.
+     * @example
+     *
+     * _.some([null, 0, 'yes', false], Boolean);
+     * // => true
+     *
+     * var users = [
+     *   { 'user': 'barney', 'active': true },
+     *   { 'user': 'fred',   'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.some(users, { 'user': 'barney', 'active': false });
+     * // => false
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.some(users, ['active', false]);
+     * // => true
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.some(users, 'active');
+     * // => true
+     */
+    function some(collection, predicate, guard) {
+      var func = isArray(collection) ? arraySome : baseSome;
+      if (guard && isIterateeCall(collection, predicate, guard)) {
+        predicate = undefined;
+      }
+      return func(collection, getIteratee(predicate, 3));
+    }
+
+    /**
+     * Creates an array of elements, sorted in ascending order by the results of
+     * running each element in a collection thru each iteratee. This method
+     * performs a stable sort, that is, it preserves the original sort order of
+     * equal elements. The iteratees are invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Collection
+     * @param {Array|Object} collection The collection to iterate over.
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [iteratees=[_.identity]] The iteratees to sort by.
+     * @returns {Array} Returns the new sorted array.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'fred',   'age': 48 },
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 },
+     *   { 'user': 'barney', 'age': 34 }
+     * ];
+     *
+     * _.sortBy(users, function(o) { return o.user; });
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     *
+     * _.sortBy(users, ['user', 'age']);
+     * // => objects for [['barney', 34], ['barney', 36], ['fred', 40], ['fred', 48]]
+     *
+     * _.sortBy(users, 'user', function(o) {
+     *   return Math.floor(o.age / 10);
+     * });
+     * // => objects for [['barney', 36], ['barney', 34], ['fred', 48], ['fred', 40]]
+     */
+    var sortBy = rest(function(collection, iteratees) {
+      if (collection == null) {
+        return [];
+      }
+      var length = iteratees.length;
+      if (length > 1 && isIterateeCall(collection, iteratees[0], iteratees[1])) {
+        iteratees = [];
+      } else if (length > 2 && isIterateeCall(iteratees[0], iteratees[1], iteratees[2])) {
+        iteratees = [iteratees[0]];
+      }
+      iteratees = (iteratees.length == 1 && isArray(iteratees[0]))
+        ? iteratees[0]
+        : baseFlatten(iteratees, 1, isFlattenableIteratee);
+
+      return baseOrderBy(collection, iteratees, []);
+    });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the timestamp of the number of milliseconds that have elapsed since
+     * the Unix epoch (1 January 1970 00:00:00 UTC).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @type {Function}
+     * @category Date
+     * @returns {number} Returns the timestamp.
+     * @example
+     *
+     * _.defer(function(stamp) {
+     *   console.log(_.now() - stamp);
+     * }, _.now());
+     * // => Logs the number of milliseconds it took for the deferred function to be invoked.
+     */
+    var now = Date.now;
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The opposite of `_.before`; this method creates a function that invokes
+     * `func` once it's called `n` or more times.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {number} n The number of calls before `func` is invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var saves = ['profile', 'settings'];
+     *
+     * var done = _.after(saves.length, function() {
+     *   console.log('done saving!');
+     * });
+     *
+     * _.forEach(saves, function(type) {
+     *   asyncSave({ 'type': type, 'complete': done });
+     * });
+     * // => Logs 'done saving!' after the two async saves have completed.
+     */
+    function after(n, func) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n < 1) {
+          return func.apply(this, arguments);
+        }
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func`, with up to `n` arguments,
+     * ignoring any additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @param {number} [n=func.length] The arity cap.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.ary(parseInt, 1));
+     * // => [6, 8, 10]
+     */
+    function ary(func, n, guard) {
+      n = guard ? undefined : n;
+      n = (func && n == null) ? func.length : n;
+      return createWrapper(func, ARY_FLAG, undefined, undefined, undefined, undefined, n);
+    }
+
+    /**
+     * Creates a function that invokes `func`, with the `this` binding and arguments
+     * of the created function, while it's called less than `n` times. Subsequent
+     * calls to the created function return the result of the last `func` invocation.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {number} n The number of calls at which `func` is no longer invoked.
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * jQuery(element).on('click', _.before(5, addContactToList));
+     * // => allows adding up to 4 contacts to the list
+     */
+    function before(n, func) {
+      var result;
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      n = toInteger(n);
+      return function() {
+        if (--n > 0) {
+          result = func.apply(this, arguments);
+        }
+        if (n <= 1) {
+          func = undefined;
+        }
+        return result;
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of `thisArg`
+     * and `partials` prepended to the arguments it receives.
+     *
+     * The `_.bind.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** Unlike native `Function#bind` this method doesn't set the "length"
+     * property of bound functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to bind.
+     * @param {*} thisArg The `this` binding of `func`.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var greet = function(greeting, punctuation) {
+     *   return greeting + ' ' + this.user + punctuation;
+     * };
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * var bound = _.bind(greet, object, 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bind(greet, object, _, '!');
+     * bound('hi');
+     * // => 'hi fred!'
+     */
+    var bind = rest(function(func, thisArg, partials) {
+      var bitmask = BIND_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getPlaceholder(bind));
+        bitmask |= PARTIAL_FLAG;
+      }
+      return createWrapper(func, bitmask, thisArg, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes the method at `object[key]` with `partials`
+     * prepended to the arguments it receives.
+     *
+     * This method differs from `_.bind` by allowing bound functions to reference
+     * methods that may be redefined or don't yet exist. See
+     * [Peter Michaux's article](http://peter.michaux.ca/articles/lazy-function-definition-pattern)
+     * for more details.
+     *
+     * The `_.bindKey.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Function
+     * @param {Object} object The object to invoke the method on.
+     * @param {string} key The key of the method.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new bound function.
+     * @example
+     *
+     * var object = {
+     *   'user': 'fred',
+     *   'greet': function(greeting, punctuation) {
+     *     return greeting + ' ' + this.user + punctuation;
+     *   }
+     * };
+     *
+     * var bound = _.bindKey(object, 'greet', 'hi');
+     * bound('!');
+     * // => 'hi fred!'
+     *
+     * object.greet = function(greeting, punctuation) {
+     *   return greeting + 'ya ' + this.user + punctuation;
+     * };
+     *
+     * bound('!');
+     * // => 'hiya fred!'
+     *
+     * // Bound with placeholders.
+     * var bound = _.bindKey(object, 'greet', _, '!');
+     * bound('hi');
+     * // => 'hiya fred!'
+     */
+    var bindKey = rest(function(object, key, partials) {
+      var bitmask = BIND_FLAG | BIND_KEY_FLAG;
+      if (partials.length) {
+        var holders = replaceHolders(partials, getPlaceholder(bindKey));
+        bitmask |= PARTIAL_FLAG;
+      }
+      return createWrapper(key, bitmask, object, partials, holders);
+    });
+
+    /**
+     * Creates a function that accepts arguments of `func` and either invokes
+     * `func` returning its result, if at least `arity` number of arguments have
+     * been provided, or returns a function that accepts the remaining `func`
+     * arguments, and so on. The arity of `func` may be specified if `func.length`
+     * is not sufficient.
+     *
+     * The `_.curry.placeholder` value, which defaults to `_` in monolithic builds,
+     * may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curry(abc);
+     *
+     * curried(1)(2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2)(3);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(1)(_, 3)(2);
+     * // => [1, 2, 3]
+     */
+    function curry(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrapper(func, CURRY_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curry.placeholder;
+      return result;
+    }
+
+    /**
+     * This method is like `_.curry` except that arguments are applied to `func`
+     * in the manner of `_.partialRight` instead of `_.partial`.
+     *
+     * The `_.curryRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for provided arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of curried functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to curry.
+     * @param {number} [arity=func.length] The arity of `func`.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the new curried function.
+     * @example
+     *
+     * var abc = function(a, b, c) {
+     *   return [a, b, c];
+     * };
+     *
+     * var curried = _.curryRight(abc);
+     *
+     * curried(3)(2)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(2, 3)(1);
+     * // => [1, 2, 3]
+     *
+     * curried(1, 2, 3);
+     * // => [1, 2, 3]
+     *
+     * // Curried with placeholders.
+     * curried(3)(1, _)(2);
+     * // => [1, 2, 3]
+     */
+    function curryRight(func, arity, guard) {
+      arity = guard ? undefined : arity;
+      var result = createWrapper(func, CURRY_RIGHT_FLAG, undefined, undefined, undefined, undefined, undefined, arity);
+      result.placeholder = curryRight.placeholder;
+      return result;
+    }
+
+    /**
+     * Creates a debounced function that delays invoking `func` until after `wait`
+     * milliseconds have elapsed since the last time the debounced function was
+     * invoked. The debounced function comes with a `cancel` method to cancel
+     * delayed `func` invocations and a `flush` method to immediately invoke them.
+     * Provide an options object to indicate whether `func` should be invoked on
+     * the leading and/or trailing edge of the `wait` timeout. The `func` is invoked
+     * with the last arguments provided to the debounced function. Subsequent calls
+     * to the debounced function return the result of the last `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is invoked
+     * on the trailing edge of the timeout only if the debounced function is
+     * invoked more than once during the `wait` timeout.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.debounce` and `_.throttle`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to debounce.
+     * @param {number} [wait=0] The number of milliseconds to delay.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=false]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {number} [options.maxWait]
+     *  The maximum time `func` is allowed to be delayed before it's invoked.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new debounced function.
+     * @example
+     *
+     * // Avoid costly calculations while the window size is in flux.
+     * jQuery(window).on('resize', _.debounce(calculateLayout, 150));
+     *
+     * // Invoke `sendMail` when clicked, debouncing subsequent calls.
+     * jQuery(element).on('click', _.debounce(sendMail, 300, {
+     *   'leading': true,
+     *   'trailing': false
+     * }));
+     *
+     * // Ensure `batchLog` is invoked once after 1 second of debounced calls.
+     * var debounced = _.debounce(batchLog, 250, { 'maxWait': 1000 });
+     * var source = new EventSource('/stream');
+     * jQuery(source).on('message', debounced);
+     *
+     * // Cancel the trailing debounced invocation.
+     * jQuery(window).on('popstate', debounced.cancel);
+     */
+    function debounce(func, wait, options) {
+      var lastArgs,
+          lastThis,
+          maxWait,
+          result,
+          timerId,
+          lastCallTime = 0,
+          lastInvokeTime = 0,
+          leading = false,
+          maxing = false,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      wait = toNumber(wait) || 0;
+      if (isObject(options)) {
+        leading = !!options.leading;
+        maxing = 'maxWait' in options;
+        maxWait = maxing ? nativeMax(toNumber(options.maxWait) || 0, wait) : maxWait;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+
+      function invokeFunc(time) {
+        var args = lastArgs,
+            thisArg = lastThis;
+
+        lastArgs = lastThis = undefined;
+        lastInvokeTime = time;
+        result = func.apply(thisArg, args);
+        return result;
+      }
+
+      function leadingEdge(time) {
+        // Reset any `maxWait` timer.
+        lastInvokeTime = time;
+        // Start the timer for the trailing edge.
+        timerId = setTimeout(timerExpired, wait);
+        // Invoke the leading edge.
+        return leading ? invokeFunc(time) : result;
+      }
+
+      function remainingWait(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime,
+            result = wait - timeSinceLastCall;
+
+        return maxing ? nativeMin(result, maxWait - timeSinceLastInvoke) : result;
+      }
+
+      function shouldInvoke(time) {
+        var timeSinceLastCall = time - lastCallTime,
+            timeSinceLastInvoke = time - lastInvokeTime;
+
+        // Either this is the first call, activity has stopped and we're at the
+        // trailing edge, the system time has gone backwards and we're treating
+        // it as the trailing edge, or we've hit the `maxWait` limit.
+        return (!lastCallTime || (timeSinceLastCall >= wait) ||
+          (timeSinceLastCall < 0) || (maxing && timeSinceLastInvoke >= maxWait));
+      }
+
+      function timerExpired() {
+        var time = now();
+        if (shouldInvoke(time)) {
+          return trailingEdge(time);
+        }
+        // Restart the timer.
+        timerId = setTimeout(timerExpired, remainingWait(time));
+      }
+
+      function trailingEdge(time) {
+        clearTimeout(timerId);
+        timerId = undefined;
+
+        // Only invoke if we have `lastArgs` which means `func` has been
+        // debounced at least once.
+        if (trailing && lastArgs) {
+          return invokeFunc(time);
+        }
+        lastArgs = lastThis = undefined;
+        return result;
+      }
+
+      function cancel() {
+        if (timerId !== undefined) {
+          clearTimeout(timerId);
+        }
+        lastCallTime = lastInvokeTime = 0;
+        lastArgs = lastThis = timerId = undefined;
+      }
+
+      function flush() {
+        return timerId === undefined ? result : trailingEdge(now());
+      }
+
+      function debounced() {
+        var time = now(),
+            isInvoking = shouldInvoke(time);
+
+        lastArgs = arguments;
+        lastThis = this;
+        lastCallTime = time;
+
+        if (isInvoking) {
+          if (timerId === undefined) {
+            return leadingEdge(lastCallTime);
+          }
+          if (maxing) {
+            // Handle invocations in a tight loop.
+            clearTimeout(timerId);
+            timerId = setTimeout(timerExpired, wait);
+            return invokeFunc(lastCallTime);
+          }
+        }
+        if (timerId === undefined) {
+          timerId = setTimeout(timerExpired, wait);
+        }
+        return result;
+      }
+      debounced.cancel = cancel;
+      debounced.flush = flush;
+      return debounced;
+    }
+
+    /**
+     * Defers invoking the `func` until the current call stack has cleared. Any
+     * additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to defer.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.defer(function(text) {
+     *   console.log(text);
+     * }, 'deferred');
+     * // => Logs 'deferred' after one or more milliseconds.
+     */
+    var defer = rest(function(func, args) {
+      return baseDelay(func, 1, args);
+    });
+
+    /**
+     * Invokes `func` after `wait` milliseconds. Any additional arguments are
+     * provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to delay.
+     * @param {number} wait The number of milliseconds to delay invocation.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {number} Returns the timer id.
+     * @example
+     *
+     * _.delay(function(text) {
+     *   console.log(text);
+     * }, 1000, 'later');
+     * // => Logs 'later' after one second.
+     */
+    var delay = rest(function(func, wait, args) {
+      return baseDelay(func, toNumber(wait) || 0, args);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments reversed.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to flip arguments for.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var flipped = _.flip(function() {
+     *   return _.toArray(arguments);
+     * });
+     *
+     * flipped('a', 'b', 'c', 'd');
+     * // => ['d', 'c', 'b', 'a']
+     */
+    function flip(func) {
+      return createWrapper(func, FLIP_FLAG);
+    }
+
+    /**
+     * Creates a function that memoizes the result of `func`. If `resolver` is
+     * provided, it determines the cache key for storing the result based on the
+     * arguments provided to the memoized function. By default, the first argument
+     * provided to the memoized function is used as the map cache key. The `func`
+     * is invoked with the `this` binding of the memoized function.
+     *
+     * **Note:** The cache is exposed as the `cache` property on the memoized
+     * function. Its creation may be customized by replacing the `_.memoize.Cache`
+     * constructor with one whose instances implement the
+     * [`Map`](http://ecma-international.org/ecma-262/6.0/#sec-properties-of-the-map-prototype-object)
+     * method interface of `delete`, `get`, `has`, and `set`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to have its output memoized.
+     * @param {Function} [resolver] The function to resolve the cache key.
+     * @returns {Function} Returns the new memoizing function.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2 };
+     * var other = { 'c': 3, 'd': 4 };
+     *
+     * var values = _.memoize(_.values);
+     * values(object);
+     * // => [1, 2]
+     *
+     * values(other);
+     * // => [3, 4]
+     *
+     * object.a = 2;
+     * values(object);
+     * // => [1, 2]
+     *
+     * // Modify the result cache.
+     * values.cache.set(object, ['a', 'b']);
+     * values(object);
+     * // => ['a', 'b']
+     *
+     * // Replace `_.memoize.Cache`.
+     * _.memoize.Cache = WeakMap;
+     */
+    function memoize(func, resolver) {
+      if (typeof func != 'function' || (resolver && typeof resolver != 'function')) {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      var memoized = function() {
+        var args = arguments,
+            key = resolver ? resolver.apply(this, args) : args[0],
+            cache = memoized.cache;
+
+        if (cache.has(key)) {
+          return cache.get(key);
+        }
+        var result = func.apply(this, args);
+        memoized.cache = cache.set(key, result);
+        return result;
+      };
+      memoized.cache = new (memoize.Cache || MapCache);
+      return memoized;
+    }
+
+    // Assign cache to `_.memoize`.
+    memoize.Cache = MapCache;
+
+    /**
+     * Creates a function that negates the result of the predicate `func`. The
+     * `func` predicate is invoked with the `this` binding and arguments of the
+     * created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} predicate The predicate to negate.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function isEven(n) {
+     *   return n % 2 == 0;
+     * }
+     *
+     * _.filter([1, 2, 3, 4, 5, 6], _.negate(isEven));
+     * // => [1, 3, 5]
+     */
+    function negate(predicate) {
+      if (typeof predicate != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      return function() {
+        return !predicate.apply(this, arguments);
+      };
+    }
+
+    /**
+     * Creates a function that is restricted to invoking `func` once. Repeat calls
+     * to the function return the value of the first invocation. The `func` is
+     * invoked with the `this` binding and arguments of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to restrict.
+     * @returns {Function} Returns the new restricted function.
+     * @example
+     *
+     * var initialize = _.once(createApplication);
+     * initialize();
+     * initialize();
+     * // `initialize` invokes `createApplication` once
+     */
+    function once(func) {
+      return before(2, func);
+    }
+
+    /**
+     * Creates a function that invokes `func` with arguments transformed by
+     * corresponding `transforms`.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Function
+     * @param {Function} func The function to wrap.
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [transforms[_.identity]] The functions to transform.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * function doubled(n) {
+     *   return n * 2;
+     * }
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var func = _.overArgs(function(x, y) {
+     *   return [x, y];
+     * }, square, doubled);
+     *
+     * func(9, 3);
+     * // => [81, 6]
+     *
+     * func(10, 5);
+     * // => [100, 10]
+     */
+    var overArgs = rest(function(func, transforms) {
+      transforms = (transforms.length == 1 && isArray(transforms[0]))
+        ? arrayMap(transforms[0], baseUnary(getIteratee()))
+        : arrayMap(baseFlatten(transforms, 1, isFlattenableIteratee), baseUnary(getIteratee()));
+
+      var funcsLength = transforms.length;
+      return rest(function(args) {
+        var index = -1,
+            length = nativeMin(args.length, funcsLength);
+
+        while (++index < length) {
+          args[index] = transforms[index].call(this, args[index]);
+        }
+        return apply(func, this, args);
+      });
+    });
+
+    /**
+     * Creates a function that invokes `func` with `partials` prepended to the
+     * arguments it receives. This method is like `_.bind` except it does **not**
+     * alter the `this` binding.
+     *
+     * The `_.partial.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.2.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) {
+     *   return greeting + ' ' + name;
+     * };
+     *
+     * var sayHelloTo = _.partial(greet, 'hello');
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     *
+     * // Partially applied with placeholders.
+     * var greetFred = _.partial(greet, _, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     */
+    var partial = rest(function(func, partials) {
+      var holders = replaceHolders(partials, getPlaceholder(partial));
+      return createWrapper(func, PARTIAL_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * This method is like `_.partial` except that partially applied arguments
+     * are appended to the arguments it receives.
+     *
+     * The `_.partialRight.placeholder` value, which defaults to `_` in monolithic
+     * builds, may be used as a placeholder for partially applied arguments.
+     *
+     * **Note:** This method doesn't set the "length" property of partially
+     * applied functions.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Function
+     * @param {Function} func The function to partially apply arguments to.
+     * @param {...*} [partials] The arguments to be partially applied.
+     * @returns {Function} Returns the new partially applied function.
+     * @example
+     *
+     * var greet = function(greeting, name) {
+     *   return greeting + ' ' + name;
+     * };
+     *
+     * var greetFred = _.partialRight(greet, 'fred');
+     * greetFred('hi');
+     * // => 'hi fred'
+     *
+     * // Partially applied with placeholders.
+     * var sayHelloTo = _.partialRight(greet, 'hello', _);
+     * sayHelloTo('fred');
+     * // => 'hello fred'
+     */
+    var partialRight = rest(function(func, partials) {
+      var holders = replaceHolders(partials, getPlaceholder(partialRight));
+      return createWrapper(func, PARTIAL_RIGHT_FLAG, undefined, partials, holders);
+    });
+
+    /**
+     * Creates a function that invokes `func` with arguments arranged according
+     * to the specified `indexes` where the argument value at the first index is
+     * provided as the first argument, the argument value at the second index is
+     * provided as the second argument, and so on.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Function
+     * @param {Function} func The function to rearrange arguments for.
+     * @param {...(number|number[])} indexes The arranged argument indexes.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var rearged = _.rearg(function(a, b, c) {
+     *   return [a, b, c];
+     * }, 2, 0, 1);
+     *
+     * rearged('b', 'c', 'a')
+     * // => ['a', 'b', 'c']
+     */
+    var rearg = rest(function(func, indexes) {
+      return createWrapper(func, REARG_FLAG, undefined, undefined, undefined, baseFlatten(indexes, 1));
+    });
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * created function and arguments from `start` and beyond provided as
+     * an array.
+     *
+     * **Note:** This method is based on the
+     * [rest parameter](https://mdn.io/rest_parameters).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to apply a rest parameter to.
+     * @param {number} [start=func.length-1] The start position of the rest parameter.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.rest(function(what, names) {
+     *   return what + ' ' + _.initial(names).join(', ') +
+     *     (_.size(names) > 1 ? ', & ' : '') + _.last(names);
+     * });
+     *
+     * say('hello', 'fred', 'barney', 'pebbles');
+     * // => 'hello fred, barney, & pebbles'
+     */
+    function rest(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = nativeMax(start === undefined ? (func.length - 1) : toInteger(start), 0);
+      return function() {
+        var args = arguments,
+            index = -1,
+            length = nativeMax(args.length - start, 0),
+            array = Array(length);
+
+        while (++index < length) {
+          array[index] = args[start + index];
+        }
+        switch (start) {
+          case 0: return func.call(this, array);
+          case 1: return func.call(this, args[0], array);
+          case 2: return func.call(this, args[0], args[1], array);
+        }
+        var otherArgs = Array(start + 1);
+        index = -1;
+        while (++index < start) {
+          otherArgs[index] = args[index];
+        }
+        otherArgs[start] = array;
+        return apply(func, this, otherArgs);
+      };
+    }
+
+    /**
+     * Creates a function that invokes `func` with the `this` binding of the
+     * create function and an array of arguments much like
+     * [`Function#apply`](http://www.ecma-international.org/ecma-262/6.0/#sec-function.prototype.apply).
+     *
+     * **Note:** This method is based on the
+     * [spread operator](https://mdn.io/spread_operator).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Function
+     * @param {Function} func The function to spread arguments over.
+     * @param {number} [start=0] The start position of the spread.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var say = _.spread(function(who, what) {
+     *   return who + ' says ' + what;
+     * });
+     *
+     * say(['fred', 'hello']);
+     * // => 'fred says hello'
+     *
+     * var numbers = Promise.all([
+     *   Promise.resolve(40),
+     *   Promise.resolve(36)
+     * ]);
+     *
+     * numbers.then(_.spread(function(x, y) {
+     *   return x + y;
+     * }));
+     * // => a Promise of 76
+     */
+    function spread(func, start) {
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      start = start === undefined ? 0 : nativeMax(toInteger(start), 0);
+      return rest(function(args) {
+        var array = args[start],
+            otherArgs = castSlice(args, 0, start);
+
+        if (array) {
+          arrayPush(otherArgs, array);
+        }
+        return apply(func, this, otherArgs);
+      });
+    }
+
+    /**
+     * Creates a throttled function that only invokes `func` at most once per
+     * every `wait` milliseconds. The throttled function comes with a `cancel`
+     * method to cancel delayed `func` invocations and a `flush` method to
+     * immediately invoke them. Provide an options object to indicate whether
+     * `func` should be invoked on the leading and/or trailing edge of the `wait`
+     * timeout. The `func` is invoked with the last arguments provided to the
+     * throttled function. Subsequent calls to the throttled function return the
+     * result of the last `func` invocation.
+     *
+     * **Note:** If `leading` and `trailing` options are `true`, `func` is
+     * invoked on the trailing edge of the timeout only if the throttled function
+     * is invoked more than once during the `wait` timeout.
+     *
+     * See [David Corbacho's article](https://css-tricks.com/debouncing-throttling-explained-examples/)
+     * for details over the differences between `_.throttle` and `_.debounce`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {Function} func The function to throttle.
+     * @param {number} [wait=0] The number of milliseconds to throttle invocations to.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.leading=true]
+     *  Specify invoking on the leading edge of the timeout.
+     * @param {boolean} [options.trailing=true]
+     *  Specify invoking on the trailing edge of the timeout.
+     * @returns {Function} Returns the new throttled function.
+     * @example
+     *
+     * // Avoid excessively updating the position while scrolling.
+     * jQuery(window).on('scroll', _.throttle(updatePosition, 100));
+     *
+     * // Invoke `renewToken` when the click event is fired, but not more than once every 5 minutes.
+     * var throttled = _.throttle(renewToken, 300000, { 'trailing': false });
+     * jQuery(element).on('click', throttled);
+     *
+     * // Cancel the trailing throttled invocation.
+     * jQuery(window).on('popstate', throttled.cancel);
+     */
+    function throttle(func, wait, options) {
+      var leading = true,
+          trailing = true;
+
+      if (typeof func != 'function') {
+        throw new TypeError(FUNC_ERROR_TEXT);
+      }
+      if (isObject(options)) {
+        leading = 'leading' in options ? !!options.leading : leading;
+        trailing = 'trailing' in options ? !!options.trailing : trailing;
+      }
+      return debounce(func, wait, {
+        'leading': leading,
+        'maxWait': wait,
+        'trailing': trailing
+      });
+    }
+
+    /**
+     * Creates a function that accepts up to one argument, ignoring any
+     * additional arguments.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Function
+     * @param {Function} func The function to cap arguments for.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * _.map(['6', '8', '10'], _.unary(parseInt));
+     * // => [6, 8, 10]
+     */
+    function unary(func) {
+      return ary(func, 1);
+    }
+
+    /**
+     * Creates a function that provides `value` to the wrapper function as its
+     * first argument. Any additional arguments provided to the function are
+     * appended to those provided to the wrapper function. The wrapper is invoked
+     * with the `this` binding of the created function.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Function
+     * @param {*} value The value to wrap.
+     * @param {Function} [wrapper=identity] The wrapper function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var p = _.wrap(_.escape, function(func, text) {
+     *   return '<p>' + func(text) + '</p>';
+     * });
+     *
+     * p('fred, barney, & pebbles');
+     * // => '<p>fred, barney, &amp; pebbles</p>'
+     */
+    function wrap(value, wrapper) {
+      wrapper = wrapper == null ? identity : wrapper;
+      return partial(wrapper, value);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Casts `value` as an array if it's not one.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.4.0
+     * @category Lang
+     * @param {*} value The value to inspect.
+     * @returns {Array} Returns the cast array.
+     * @example
+     *
+     * _.castArray(1);
+     * // => [1]
+     *
+     * _.castArray({ 'a': 1 });
+     * // => [{ 'a': 1 }]
+     *
+     * _.castArray('abc');
+     * // => ['abc']
+     *
+     * _.castArray(null);
+     * // => [null]
+     *
+     * _.castArray(undefined);
+     * // => [undefined]
+     *
+     * _.castArray();
+     * // => []
+     *
+     * var array = [1, 2, 3];
+     * console.log(_.castArray(array) === array);
+     * // => true
+     */
+    function castArray() {
+      if (!arguments.length) {
+        return [];
+      }
+      var value = arguments[0];
+      return isArray(value) ? value : [value];
+    }
+
+    /**
+     * Creates a shallow clone of `value`.
+     *
+     * **Note:** This method is loosely based on the
+     * [structured clone algorithm](https://mdn.io/Structured_clone_algorithm)
+     * and supports cloning arrays, array buffers, booleans, date objects, maps,
+     * numbers, `Object` objects, regexes, sets, strings, symbols, and typed
+     * arrays. The own enumerable properties of `arguments` objects are cloned
+     * as plain objects. An empty object is returned for uncloneable values such
+     * as error objects, functions, DOM nodes, and WeakMaps.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @returns {*} Returns the cloned value.
+     * @see _.cloneDeep
+     * @example
+     *
+     * var objects = [{ 'a': 1 }, { 'b': 2 }];
+     *
+     * var shallow = _.clone(objects);
+     * console.log(shallow[0] === objects[0]);
+     * // => true
+     */
+    function clone(value) {
+      return baseClone(value, false, true);
+    }
+
+    /**
+     * This method is like `_.clone` except that it accepts `customizer` which
+     * is invoked to produce the cloned value. If `customizer` returns `undefined`,
+     * cloning is handled by the method instead. The `customizer` is invoked with
+     * up to four arguments; (value [, index|key, object, stack]).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to clone.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @returns {*} Returns the cloned value.
+     * @see _.cloneDeepWith
+     * @example
+     *
+     * function customizer(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(false);
+     *   }
+     * }
+     *
+     * var el = _.cloneWith(document.body, customizer);
+     *
+     * console.log(el === document.body);
+     * // => false
+     * console.log(el.nodeName);
+     * // => 'BODY'
+     * console.log(el.childNodes.length);
+     * // => 0
+     */
+    function cloneWith(value, customizer) {
+      return baseClone(value, false, true, customizer);
+    }
+
+    /**
+     * This method is like `_.clone` except that it recursively clones `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Lang
+     * @param {*} value The value to recursively clone.
+     * @returns {*} Returns the deep cloned value.
+     * @see _.clone
+     * @example
+     *
+     * var objects = [{ 'a': 1 }, { 'b': 2 }];
+     *
+     * var deep = _.cloneDeep(objects);
+     * console.log(deep[0] === objects[0]);
+     * // => false
+     */
+    function cloneDeep(value) {
+      return baseClone(value, true, true);
+    }
+
+    /**
+     * This method is like `_.cloneWith` except that it recursively clones `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to recursively clone.
+     * @param {Function} [customizer] The function to customize cloning.
+     * @returns {*} Returns the deep cloned value.
+     * @see _.cloneWith
+     * @example
+     *
+     * function customizer(value) {
+     *   if (_.isElement(value)) {
+     *     return value.cloneNode(true);
+     *   }
+     * }
+     *
+     * var el = _.cloneDeepWith(document.body, customizer);
+     *
+     * console.log(el === document.body);
+     * // => false
+     * console.log(el.nodeName);
+     * // => 'BODY'
+     * console.log(el.childNodes.length);
+     * // => 20
+     */
+    function cloneDeepWith(value, customizer) {
+      return baseClone(value, true, true, customizer);
+    }
+
+    /**
+     * Performs a
+     * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero)
+     * comparison between two values to determine if they are equivalent.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if the values are equivalent, else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var other = { 'user': 'fred' };
+     *
+     * _.eq(object, object);
+     * // => true
+     *
+     * _.eq(object, other);
+     * // => false
+     *
+     * _.eq('a', 'a');
+     * // => true
+     *
+     * _.eq('a', Object('a'));
+     * // => false
+     *
+     * _.eq(NaN, NaN);
+     * // => true
+     */
+    function eq(value, other) {
+      return value === other || (value !== value && other !== other);
+    }
+
+    /**
+     * Checks if `value` is greater than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than `other`,
+     *  else `false`.
+     * @see _.lt
+     * @example
+     *
+     * _.gt(3, 1);
+     * // => true
+     *
+     * _.gt(3, 3);
+     * // => false
+     *
+     * _.gt(1, 3);
+     * // => false
+     */
+    var gt = createRelationalOperation(baseGt);
+
+    /**
+     * Checks if `value` is greater than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is greater than or equal to
+     *  `other`, else `false`.
+     * @see _.lte
+     * @example
+     *
+     * _.gte(3, 1);
+     * // => true
+     *
+     * _.gte(3, 3);
+     * // => true
+     *
+     * _.gte(1, 3);
+     * // => false
+     */
+    var gte = createRelationalOperation(function(value, other) {
+      return value >= other;
+    });
+
+    /**
+     * Checks if `value` is likely an `arguments` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isArguments(function() { return arguments; }());
+     * // => true
+     *
+     * _.isArguments([1, 2, 3]);
+     * // => false
+     */
+    function isArguments(value) {
+      // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode.
+      return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') &&
+        (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag);
+    }
+
+    /**
+     * Checks if `value` is classified as an `Array` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @type {Function}
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isArray([1, 2, 3]);
+     * // => true
+     *
+     * _.isArray(document.body.children);
+     * // => false
+     *
+     * _.isArray('abc');
+     * // => false
+     *
+     * _.isArray(_.noop);
+     * // => false
+     */
+    var isArray = Array.isArray;
+
+    /**
+     * Checks if `value` is classified as an `ArrayBuffer` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isArrayBuffer(new ArrayBuffer(2));
+     * // => true
+     *
+     * _.isArrayBuffer(new Array(2));
+     * // => false
+     */
+    function isArrayBuffer(value) {
+      return isObjectLike(value) && objectToString.call(value) == arrayBufferTag;
+    }
+
+    /**
+     * Checks if `value` is array-like. A value is considered array-like if it's
+     * not a function and has a `value.length` that's an integer greater than or
+     * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is array-like, else `false`.
+     * @example
+     *
+     * _.isArrayLike([1, 2, 3]);
+     * // => true
+     *
+     * _.isArrayLike(document.body.children);
+     * // => true
+     *
+     * _.isArrayLike('abc');
+     * // => true
+     *
+     * _.isArrayLike(_.noop);
+     * // => false
+     */
+    function isArrayLike(value) {
+      return value != null && isLength(getLength(value)) && !isFunction(value);
+    }
+
+    /**
+     * This method is like `_.isArrayLike` except that it also checks if `value`
+     * is an object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an array-like object,
+     *  else `false`.
+     * @example
+     *
+     * _.isArrayLikeObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isArrayLikeObject(document.body.children);
+     * // => true
+     *
+     * _.isArrayLikeObject('abc');
+     * // => false
+     *
+     * _.isArrayLikeObject(_.noop);
+     * // => false
+     */
+    function isArrayLikeObject(value) {
+      return isObjectLike(value) && isArrayLike(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a boolean primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isBoolean(false);
+     * // => true
+     *
+     * _.isBoolean(null);
+     * // => false
+     */
+    function isBoolean(value) {
+      return value === true || value === false ||
+        (isObjectLike(value) && objectToString.call(value) == boolTag);
+    }
+
+    /**
+     * Checks if `value` is a buffer.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a buffer, else `false`.
+     * @example
+     *
+     * _.isBuffer(new Buffer(2));
+     * // => true
+     *
+     * _.isBuffer(new Uint8Array(2));
+     * // => false
+     */
+    var isBuffer = !Buffer ? constant(false) : function(value) {
+      return value instanceof Buffer;
+    };
+
+    /**
+     * Checks if `value` is classified as a `Date` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isDate(new Date);
+     * // => true
+     *
+     * _.isDate('Mon April 23 2012');
+     * // => false
+     */
+    function isDate(value) {
+      return isObjectLike(value) && objectToString.call(value) == dateTag;
+    }
+
+    /**
+     * Checks if `value` is likely a DOM element.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a DOM element,
+     *  else `false`.
+     * @example
+     *
+     * _.isElement(document.body);
+     * // => true
+     *
+     * _.isElement('<body>');
+     * // => false
+     */
+    function isElement(value) {
+      return !!value && value.nodeType === 1 && isObjectLike(value) && !isPlainObject(value);
+    }
+
+    /**
+     * Checks if `value` is an empty object, collection, map, or set.
+     *
+     * Objects are considered empty if they have no own enumerable string keyed
+     * properties.
+     *
+     * Array-like values such as `arguments` objects, arrays, buffers, strings, or
+     * jQuery-like collections are considered empty if they have a `length` of `0`.
+     * Similarly, maps and sets are considered empty if they have a `size` of `0`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is empty, else `false`.
+     * @example
+     *
+     * _.isEmpty(null);
+     * // => true
+     *
+     * _.isEmpty(true);
+     * // => true
+     *
+     * _.isEmpty(1);
+     * // => true
+     *
+     * _.isEmpty([1, 2, 3]);
+     * // => false
+     *
+     * _.isEmpty({ 'a': 1 });
+     * // => false
+     */
+    function isEmpty(value) {
+      if (isArrayLike(value) &&
+          (isArray(value) || isString(value) || isFunction(value.splice) ||
+            isArguments(value) || isBuffer(value))) {
+        return !value.length;
+      }
+      if (isObjectLike(value)) {
+        var tag = getTag(value);
+        if (tag == mapTag || tag == setTag) {
+          return !value.size;
+        }
+      }
+      for (var key in value) {
+        if (hasOwnProperty.call(value, key)) {
+          return false;
+        }
+      }
+      return !(nonEnumShadows && keys(value).length);
+    }
+
+    /**
+     * Performs a deep comparison between two values to determine if they are
+     * equivalent.
+     *
+     * **Note:** This method supports comparing arrays, array buffers, booleans,
+     * date objects, error objects, maps, numbers, `Object` objects, regexes,
+     * sets, strings, symbols, and typed arrays. `Object` objects are compared
+     * by their own, not inherited, enumerable properties. Functions and DOM
+     * nodes are **not** supported.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if the values are equivalent,
+     *  else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var other = { 'user': 'fred' };
+     *
+     * _.isEqual(object, other);
+     * // => true
+     *
+     * object === other;
+     * // => false
+     */
+    function isEqual(value, other) {
+      return baseIsEqual(value, other);
+    }
+
+    /**
+     * This method is like `_.isEqual` except that it accepts `customizer` which
+     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+     * are handled by the method instead. The `customizer` is invoked with up to
+     * six arguments: (objValue, othValue [, index|key, object, other, stack]).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if the values are equivalent,
+     *  else `false`.
+     * @example
+     *
+     * function isGreeting(value) {
+     *   return /^h(?:i|ello)$/.test(value);
+     * }
+     *
+     * function customizer(objValue, othValue) {
+     *   if (isGreeting(objValue) && isGreeting(othValue)) {
+     *     return true;
+     *   }
+     * }
+     *
+     * var array = ['hello', 'goodbye'];
+     * var other = ['hi', 'goodbye'];
+     *
+     * _.isEqualWith(array, other, customizer);
+     * // => true
+     */
+    function isEqualWith(value, other, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      var result = customizer ? customizer(value, other) : undefined;
+      return result === undefined ? baseIsEqual(value, other, customizer) : !!result;
+    }
+
+    /**
+     * Checks if `value` is an `Error`, `EvalError`, `RangeError`, `ReferenceError`,
+     * `SyntaxError`, `TypeError`, or `URIError` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an error object,
+     *  else `false`.
+     * @example
+     *
+     * _.isError(new Error);
+     * // => true
+     *
+     * _.isError(Error);
+     * // => false
+     */
+    function isError(value) {
+      if (!isObjectLike(value)) {
+        return false;
+      }
+      return (objectToString.call(value) == errorTag) ||
+        (typeof value.message == 'string' && typeof value.name == 'string');
+    }
+
+    /**
+     * Checks if `value` is a finite primitive number.
+     *
+     * **Note:** This method is based on
+     * [`Number.isFinite`](https://mdn.io/Number/isFinite).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a finite number,
+     *  else `false`.
+     * @example
+     *
+     * _.isFinite(3);
+     * // => true
+     *
+     * _.isFinite(Number.MAX_VALUE);
+     * // => true
+     *
+     * _.isFinite(3.14);
+     * // => true
+     *
+     * _.isFinite(Infinity);
+     * // => false
+     */
+    function isFinite(value) {
+      return typeof value == 'number' && nativeIsFinite(value);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Function` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isFunction(_);
+     * // => true
+     *
+     * _.isFunction(/abc/);
+     * // => false
+     */
+    function isFunction(value) {
+      // The use of `Object#toString` avoids issues with the `typeof` operator
+      // in Safari 8 which returns 'object' for typed array and weak map constructors,
+      // and PhantomJS 1.9 which returns 'function' for `NodeList` instances.
+      var tag = isObject(value) ? objectToString.call(value) : '';
+      return tag == funcTag || tag == genTag;
+    }
+
+    /**
+     * Checks if `value` is an integer.
+     *
+     * **Note:** This method is based on
+     * [`Number.isInteger`](https://mdn.io/Number/isInteger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an integer, else `false`.
+     * @example
+     *
+     * _.isInteger(3);
+     * // => true
+     *
+     * _.isInteger(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isInteger(Infinity);
+     * // => false
+     *
+     * _.isInteger('3');
+     * // => false
+     */
+    function isInteger(value) {
+      return typeof value == 'number' && value == toInteger(value);
+    }
+
+    /**
+     * Checks if `value` is a valid array-like length.
+     *
+     * **Note:** This function is loosely based on
+     * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a valid length,
+     *  else `false`.
+     * @example
+     *
+     * _.isLength(3);
+     * // => true
+     *
+     * _.isLength(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isLength(Infinity);
+     * // => false
+     *
+     * _.isLength('3');
+     * // => false
+     */
+    function isLength(value) {
+      return typeof value == 'number' &&
+        value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is the
+     * [language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types)
+     * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`)
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is an object, else `false`.
+     * @example
+     *
+     * _.isObject({});
+     * // => true
+     *
+     * _.isObject([1, 2, 3]);
+     * // => true
+     *
+     * _.isObject(_.noop);
+     * // => true
+     *
+     * _.isObject(null);
+     * // => false
+     */
+    function isObject(value) {
+      var type = typeof value;
+      return !!value && (type == 'object' || type == 'function');
+    }
+
+    /**
+     * Checks if `value` is object-like. A value is object-like if it's not `null`
+     * and has a `typeof` result of "object".
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is object-like, else `false`.
+     * @example
+     *
+     * _.isObjectLike({});
+     * // => true
+     *
+     * _.isObjectLike([1, 2, 3]);
+     * // => true
+     *
+     * _.isObjectLike(_.noop);
+     * // => false
+     *
+     * _.isObjectLike(null);
+     * // => false
+     */
+    function isObjectLike(value) {
+      return !!value && typeof value == 'object';
+    }
+
+    /**
+     * Checks if `value` is classified as a `Map` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isMap(new Map);
+     * // => true
+     *
+     * _.isMap(new WeakMap);
+     * // => false
+     */
+    function isMap(value) {
+      return isObjectLike(value) && getTag(value) == mapTag;
+    }
+
+    /**
+     * Performs a partial deep comparison between `object` and `source` to
+     * determine if `object` contains equivalent property values. This method is
+     * equivalent to a `_.matches` function when `source` is partially applied.
+     *
+     * **Note:** This method supports comparing the same values as `_.isEqual`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * var object = { 'user': 'fred', 'age': 40 };
+     *
+     * _.isMatch(object, { 'age': 40 });
+     * // => true
+     *
+     * _.isMatch(object, { 'age': 36 });
+     * // => false
+     */
+    function isMatch(object, source) {
+      return object === source || baseIsMatch(object, source, getMatchData(source));
+    }
+
+    /**
+     * This method is like `_.isMatch` except that it accepts `customizer` which
+     * is invoked to compare values. If `customizer` returns `undefined`, comparisons
+     * are handled by the method instead. The `customizer` is invoked with five
+     * arguments: (objValue, srcValue, index|key, object, source).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {Object} object The object to inspect.
+     * @param {Object} source The object of property values to match.
+     * @param {Function} [customizer] The function to customize comparisons.
+     * @returns {boolean} Returns `true` if `object` is a match, else `false`.
+     * @example
+     *
+     * function isGreeting(value) {
+     *   return /^h(?:i|ello)$/.test(value);
+     * }
+     *
+     * function customizer(objValue, srcValue) {
+     *   if (isGreeting(objValue) && isGreeting(srcValue)) {
+     *     return true;
+     *   }
+     * }
+     *
+     * var object = { 'greeting': 'hello' };
+     * var source = { 'greeting': 'hi' };
+     *
+     * _.isMatchWith(object, source, customizer);
+     * // => true
+     */
+    function isMatchWith(object, source, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return baseIsMatch(object, source, getMatchData(source), customizer);
+    }
+
+    /**
+     * Checks if `value` is `NaN`.
+     *
+     * **Note:** This method is based on
+     * [`Number.isNaN`](https://mdn.io/Number/isNaN) and is not the same as
+     * global [`isNaN`](https://mdn.io/isNaN) which returns `true` for
+     * `undefined` and other non-number values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `NaN`, else `false`.
+     * @example
+     *
+     * _.isNaN(NaN);
+     * // => true
+     *
+     * _.isNaN(new Number(NaN));
+     * // => true
+     *
+     * isNaN(undefined);
+     * // => true
+     *
+     * _.isNaN(undefined);
+     * // => false
+     */
+    function isNaN(value) {
+      // An `NaN` primitive is the only value that is not equal to itself.
+      // Perform the `toStringTag` check first to avoid errors with some
+      // ActiveX objects in IE.
+      return isNumber(value) && value != +value;
+    }
+
+    /**
+     * Checks if `value` is a native function.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a native function,
+     *  else `false`.
+     * @example
+     *
+     * _.isNative(Array.prototype.push);
+     * // => true
+     *
+     * _.isNative(_);
+     * // => false
+     */
+    function isNative(value) {
+      if (!isObject(value)) {
+        return false;
+      }
+      var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor;
+      return pattern.test(toSource(value));
+    }
+
+    /**
+     * Checks if `value` is `null`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `null`, else `false`.
+     * @example
+     *
+     * _.isNull(null);
+     * // => true
+     *
+     * _.isNull(void 0);
+     * // => false
+     */
+    function isNull(value) {
+      return value === null;
+    }
+
+    /**
+     * Checks if `value` is `null` or `undefined`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is nullish, else `false`.
+     * @example
+     *
+     * _.isNil(null);
+     * // => true
+     *
+     * _.isNil(void 0);
+     * // => true
+     *
+     * _.isNil(NaN);
+     * // => false
+     */
+    function isNil(value) {
+      return value == null;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Number` primitive or object.
+     *
+     * **Note:** To exclude `Infinity`, `-Infinity`, and `NaN`, which are
+     * classified as numbers, use the `_.isFinite` method.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isNumber(3);
+     * // => true
+     *
+     * _.isNumber(Number.MIN_VALUE);
+     * // => true
+     *
+     * _.isNumber(Infinity);
+     * // => true
+     *
+     * _.isNumber('3');
+     * // => false
+     */
+    function isNumber(value) {
+      return typeof value == 'number' ||
+        (isObjectLike(value) && objectToString.call(value) == numberTag);
+    }
+
+    /**
+     * Checks if `value` is a plain object, that is, an object created by the
+     * `Object` constructor or one with a `[[Prototype]]` of `null`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.8.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a plain object,
+     *  else `false`.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     * }
+     *
+     * _.isPlainObject(new Foo);
+     * // => false
+     *
+     * _.isPlainObject([1, 2, 3]);
+     * // => false
+     *
+     * _.isPlainObject({ 'x': 0, 'y': 0 });
+     * // => true
+     *
+     * _.isPlainObject(Object.create(null));
+     * // => true
+     */
+    function isPlainObject(value) {
+      if (!isObjectLike(value) ||
+          objectToString.call(value) != objectTag || isHostObject(value)) {
+        return false;
+      }
+      var proto = getPrototype(value);
+      if (proto === null) {
+        return true;
+      }
+      var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor;
+      return (typeof Ctor == 'function' &&
+        Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString);
+    }
+
+    /**
+     * Checks if `value` is classified as a `RegExp` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.1.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isRegExp(/abc/);
+     * // => true
+     *
+     * _.isRegExp('/abc/');
+     * // => false
+     */
+    function isRegExp(value) {
+      return isObject(value) && objectToString.call(value) == regexpTag;
+    }
+
+    /**
+     * Checks if `value` is a safe integer. An integer is safe if it's an IEEE-754
+     * double precision number which isn't the result of a rounded unsafe integer.
+     *
+     * **Note:** This method is based on
+     * [`Number.isSafeInteger`](https://mdn.io/Number/isSafeInteger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is a safe integer,
+     *  else `false`.
+     * @example
+     *
+     * _.isSafeInteger(3);
+     * // => true
+     *
+     * _.isSafeInteger(Number.MIN_VALUE);
+     * // => false
+     *
+     * _.isSafeInteger(Infinity);
+     * // => false
+     *
+     * _.isSafeInteger('3');
+     * // => false
+     */
+    function isSafeInteger(value) {
+      return isInteger(value) && value >= -MAX_SAFE_INTEGER && value <= MAX_SAFE_INTEGER;
+    }
+
+    /**
+     * Checks if `value` is classified as a `Set` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isSet(new Set);
+     * // => true
+     *
+     * _.isSet(new WeakSet);
+     * // => false
+     */
+    function isSet(value) {
+      return isObjectLike(value) && getTag(value) == setTag;
+    }
+
+    /**
+     * Checks if `value` is classified as a `String` primitive or object.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isString('abc');
+     * // => true
+     *
+     * _.isString(1);
+     * // => false
+     */
+    function isString(value) {
+      return typeof value == 'string' ||
+        (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a `Symbol` primitive or object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isSymbol(Symbol.iterator);
+     * // => true
+     *
+     * _.isSymbol('abc');
+     * // => false
+     */
+    function isSymbol(value) {
+      return typeof value == 'symbol' ||
+        (isObjectLike(value) && objectToString.call(value) == symbolTag);
+    }
+
+    /**
+     * Checks if `value` is classified as a typed array.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isTypedArray(new Uint8Array);
+     * // => true
+     *
+     * _.isTypedArray([]);
+     * // => false
+     */
+    function isTypedArray(value) {
+      return isObjectLike(value) &&
+        isLength(value.length) && !!typedArrayTags[objectToString.call(value)];
+    }
+
+    /**
+     * Checks if `value` is `undefined`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is `undefined`, else `false`.
+     * @example
+     *
+     * _.isUndefined(void 0);
+     * // => true
+     *
+     * _.isUndefined(null);
+     * // => false
+     */
+    function isUndefined(value) {
+      return value === undefined;
+    }
+
+    /**
+     * Checks if `value` is classified as a `WeakMap` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isWeakMap(new WeakMap);
+     * // => true
+     *
+     * _.isWeakMap(new Map);
+     * // => false
+     */
+    function isWeakMap(value) {
+      return isObjectLike(value) && getTag(value) == weakMapTag;
+    }
+
+    /**
+     * Checks if `value` is classified as a `WeakSet` object.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.3.0
+     * @category Lang
+     * @param {*} value The value to check.
+     * @returns {boolean} Returns `true` if `value` is correctly classified,
+     *  else `false`.
+     * @example
+     *
+     * _.isWeakSet(new WeakSet);
+     * // => true
+     *
+     * _.isWeakSet(new Set);
+     * // => false
+     */
+    function isWeakSet(value) {
+      return isObjectLike(value) && objectToString.call(value) == weakSetTag;
+    }
+
+    /**
+     * Checks if `value` is less than `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than `other`,
+     *  else `false`.
+     * @see _.gt
+     * @example
+     *
+     * _.lt(1, 3);
+     * // => true
+     *
+     * _.lt(3, 3);
+     * // => false
+     *
+     * _.lt(3, 1);
+     * // => false
+     */
+    var lt = createRelationalOperation(baseLt);
+
+    /**
+     * Checks if `value` is less than or equal to `other`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.9.0
+     * @category Lang
+     * @param {*} value The value to compare.
+     * @param {*} other The other value to compare.
+     * @returns {boolean} Returns `true` if `value` is less than or equal to
+     *  `other`, else `false`.
+     * @see _.gte
+     * @example
+     *
+     * _.lte(1, 3);
+     * // => true
+     *
+     * _.lte(3, 3);
+     * // => true
+     *
+     * _.lte(3, 1);
+     * // => false
+     */
+    var lte = createRelationalOperation(function(value, other) {
+      return value <= other;
+    });
+
+    /**
+     * Converts `value` to an array.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the converted array.
+     * @example
+     *
+     * _.toArray({ 'a': 1, 'b': 2 });
+     * // => [1, 2]
+     *
+     * _.toArray('abc');
+     * // => ['a', 'b', 'c']
+     *
+     * _.toArray(1);
+     * // => []
+     *
+     * _.toArray(null);
+     * // => []
+     */
+    function toArray(value) {
+      if (!value) {
+        return [];
+      }
+      if (isArrayLike(value)) {
+        return isString(value) ? stringToArray(value) : copyArray(value);
+      }
+      if (iteratorSymbol && value[iteratorSymbol]) {
+        return iteratorToArray(value[iteratorSymbol]());
+      }
+      var tag = getTag(value),
+          func = tag == mapTag ? mapToArray : (tag == setTag ? setToArray : values);
+
+      return func(value);
+    }
+
+    /**
+     * Converts `value` to an integer.
+     *
+     * **Note:** This function is loosely based on
+     * [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toInteger(3);
+     * // => 3
+     *
+     * _.toInteger(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toInteger(Infinity);
+     * // => 1.7976931348623157e+308
+     *
+     * _.toInteger('3');
+     * // => 3
+     */
+    function toInteger(value) {
+      if (!value) {
+        return value === 0 ? value : 0;
+      }
+      value = toNumber(value);
+      if (value === INFINITY || value === -INFINITY) {
+        var sign = (value < 0 ? -1 : 1);
+        return sign * MAX_INTEGER;
+      }
+      var remainder = value % 1;
+      return value === value ? (remainder ? value - remainder : value) : 0;
+    }
+
+    /**
+     * Converts `value` to an integer suitable for use as the length of an
+     * array-like object.
+     *
+     * **Note:** This method is based on
+     * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toLength(3);
+     * // => 3
+     *
+     * _.toLength(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toLength(Infinity);
+     * // => 4294967295
+     *
+     * _.toLength('3');
+     * // => 3
+     */
+    function toLength(value) {
+      return value ? baseClamp(toInteger(value), 0, MAX_ARRAY_LENGTH) : 0;
+    }
+
+    /**
+     * Converts `value` to a number.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to process.
+     * @returns {number} Returns the number.
+     * @example
+     *
+     * _.toNumber(3);
+     * // => 3
+     *
+     * _.toNumber(Number.MIN_VALUE);
+     * // => 5e-324
+     *
+     * _.toNumber(Infinity);
+     * // => Infinity
+     *
+     * _.toNumber('3');
+     * // => 3
+     */
+    function toNumber(value) {
+      if (typeof value == 'number') {
+        return value;
+      }
+      if (isSymbol(value)) {
+        return NAN;
+      }
+      if (isObject(value)) {
+        var other = isFunction(value.valueOf) ? value.valueOf() : value;
+        value = isObject(other) ? (other + '') : other;
+      }
+      if (typeof value != 'string') {
+        return value === 0 ? value : +value;
+      }
+      value = value.replace(reTrim, '');
+      var isBinary = reIsBinary.test(value);
+      return (isBinary || reIsOctal.test(value))
+        ? freeParseInt(value.slice(2), isBinary ? 2 : 8)
+        : (reIsBadHex.test(value) ? NAN : +value);
+    }
+
+    /**
+     * Converts `value` to a plain object flattening inherited enumerable string
+     * keyed properties of `value` to own properties of the plain object.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {Object} Returns the converted plain object.
+     * @example
+     *
+     * function Foo() {
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.assign({ 'a': 1 }, new Foo);
+     * // => { 'a': 1, 'b': 2 }
+     *
+     * _.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+     * // => { 'a': 1, 'b': 2, 'c': 3 }
+     */
+    function toPlainObject(value) {
+      return copyObject(value, keysIn(value));
+    }
+
+    /**
+     * Converts `value` to a safe integer. A safe integer can be compared and
+     * represented correctly.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to convert.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.toSafeInteger(3);
+     * // => 3
+     *
+     * _.toSafeInteger(Number.MIN_VALUE);
+     * // => 0
+     *
+     * _.toSafeInteger(Infinity);
+     * // => 9007199254740991
+     *
+     * _.toSafeInteger('3');
+     * // => 3
+     */
+    function toSafeInteger(value) {
+      return baseClamp(toInteger(value), -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER);
+    }
+
+    /**
+     * Converts `value` to a string. An empty string is returned for `null`
+     * and `undefined` values. The sign of `-0` is preserved.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Lang
+     * @param {*} value The value to process.
+     * @returns {string} Returns the string.
+     * @example
+     *
+     * _.toString(null);
+     * // => ''
+     *
+     * _.toString(-0);
+     * // => '-0'
+     *
+     * _.toString([1, 2, 3]);
+     * // => '1,2,3'
+     */
+    function toString(value) {
+      return value == null ? '' : baseToString(value);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Assigns own enumerable string keyed properties of source objects to the
+     * destination object. Source objects are applied from left to right.
+     * Subsequent sources overwrite property assignments of previous sources.
+     *
+     * **Note:** This method mutates `object` and is loosely based on
+     * [`Object.assign`](https://mdn.io/Object/assign).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.10.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.assignIn
+     * @example
+     *
+     * function Foo() {
+     *   this.c = 3;
+     * }
+     *
+     * function Bar() {
+     *   this.e = 5;
+     * }
+     *
+     * Foo.prototype.d = 4;
+     * Bar.prototype.f = 6;
+     *
+     * _.assign({ 'a': 1 }, new Foo, new Bar);
+     * // => { 'a': 1, 'c': 3, 'e': 5 }
+     */
+    var assign = createAssigner(function(object, source) {
+      if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) {
+        copyObject(source, keys(source), object);
+        return;
+      }
+      for (var key in source) {
+        if (hasOwnProperty.call(source, key)) {
+          assignValue(object, key, source[key]);
+        }
+      }
+    });
+
+    /**
+     * This method is like `_.assign` except that it iterates over own and
+     * inherited source properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias extend
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.assign
+     * @example
+     *
+     * function Foo() {
+     *   this.b = 2;
+     * }
+     *
+     * function Bar() {
+     *   this.d = 4;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     * Bar.prototype.e = 5;
+     *
+     * _.assignIn({ 'a': 1 }, new Foo, new Bar);
+     * // => { 'a': 1, 'b': 2, 'c': 3, 'd': 4, 'e': 5 }
+     */
+    var assignIn = createAssigner(function(object, source) {
+      if (nonEnumShadows || isPrototype(source) || isArrayLike(source)) {
+        copyObject(source, keysIn(source), object);
+        return;
+      }
+      for (var key in source) {
+        assignValue(object, key, source[key]);
+      }
+    });
+
+    /**
+     * This method is like `_.assignIn` except that it accepts `customizer`
+     * which is invoked to produce the assigned values. If `customizer` returns
+     * `undefined`, assignment is handled by the method instead. The `customizer`
+     * is invoked with five arguments: (objValue, srcValue, key, object, source).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias extendWith
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @see _.assignWith
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   return _.isUndefined(objValue) ? srcValue : objValue;
+     * }
+     *
+     * var defaults = _.partialRight(_.assignInWith, customizer);
+     *
+     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var assignInWith = createAssigner(function(object, source, srcIndex, customizer) {
+      copyObject(source, keysIn(source), object, customizer);
+    });
+
+    /**
+     * This method is like `_.assign` except that it accepts `customizer`
+     * which is invoked to produce the assigned values. If `customizer` returns
+     * `undefined`, assignment is handled by the method instead. The `customizer`
+     * is invoked with five arguments: (objValue, srcValue, key, object, source).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @see _.assignInWith
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   return _.isUndefined(objValue) ? srcValue : objValue;
+     * }
+     *
+     * var defaults = _.partialRight(_.assignWith, customizer);
+     *
+     * defaults({ 'a': 1 }, { 'b': 2 }, { 'a': 3 });
+     * // => { 'a': 1, 'b': 2 }
+     */
+    var assignWith = createAssigner(function(object, source, srcIndex, customizer) {
+      copyObject(source, keys(source), object, customizer);
+    });
+
+    /**
+     * Creates an array of values corresponding to `paths` of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {...(string|string[])} [paths] The property paths of elements to pick.
+     * @returns {Array} Returns the new array of picked elements.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+     *
+     * _.at(object, ['a[0].b.c', 'a[1]']);
+     * // => [3, 4]
+     *
+     * _.at(['a', 'b', 'c'], 0, 2);
+     * // => ['a', 'c']
+     */
+    var at = rest(function(object, paths) {
+      return baseAt(object, baseFlatten(paths, 1));
+    });
+
+    /**
+     * Creates an object that inherits from the `prototype` object. If a
+     * `properties` object is given, its own enumerable string keyed properties
+     * are assigned to the created object.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.3.0
+     * @category Object
+     * @param {Object} prototype The object to inherit from.
+     * @param {Object} [properties] The properties to assign to the object.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * function Shape() {
+     *   this.x = 0;
+     *   this.y = 0;
+     * }
+     *
+     * function Circle() {
+     *   Shape.call(this);
+     * }
+     *
+     * Circle.prototype = _.create(Shape.prototype, {
+     *   'constructor': Circle
+     * });
+     *
+     * var circle = new Circle;
+     * circle instanceof Circle;
+     * // => true
+     *
+     * circle instanceof Shape;
+     * // => true
+     */
+    function create(prototype, properties) {
+      var result = baseCreate(prototype);
+      return properties ? baseAssign(result, properties) : result;
+    }
+
+    /**
+     * Assigns own and inherited enumerable string keyed properties of source
+     * objects to the destination object for all destination properties that
+     * resolve to `undefined`. Source objects are applied from left to right.
+     * Once a property is set, additional values of the same property are ignored.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.defaultsDeep
+     * @example
+     *
+     * _.defaults({ 'user': 'barney' }, { 'age': 36 }, { 'user': 'fred' });
+     * // => { 'user': 'barney', 'age': 36 }
+     */
+    var defaults = rest(function(args) {
+      args.push(undefined, assignInDefaults);
+      return apply(assignInWith, undefined, args);
+    });
+
+    /**
+     * This method is like `_.defaults` except that it recursively assigns
+     * default properties.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @see _.defaults
+     * @example
+     *
+     * _.defaultsDeep({ 'user': { 'name': 'barney' } }, { 'user': { 'name': 'fred', 'age': 36 } });
+     * // => { 'user': { 'name': 'barney', 'age': 36 } }
+     *
+     */
+    var defaultsDeep = rest(function(args) {
+      args.push(undefined, mergeDefaults);
+      return apply(mergeWith, undefined, args);
+    });
+
+    /**
+     * This method is like `_.find` except that it returns the key of the first
+     * element `predicate` returns truthy for instead of the element itself.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category Object
+     * @param {Object} object The object to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {string|undefined} Returns the key of the matched element,
+     *  else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findKey(users, function(o) { return o.age < 40; });
+     * // => 'barney' (iteration order is not guaranteed)
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findKey(users, { 'age': 1, 'active': true });
+     * // => 'pebbles'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findKey(users, ['active', false]);
+     * // => 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findKey(users, 'active');
+     * // => 'barney'
+     */
+    function findKey(object, predicate) {
+      return baseFind(object, getIteratee(predicate, 3), baseForOwn, true);
+    }
+
+    /**
+     * This method is like `_.findKey` except that it iterates over elements of
+     * a collection in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to search.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per iteration.
+     * @returns {string|undefined} Returns the key of the matched element,
+     *  else `undefined`.
+     * @example
+     *
+     * var users = {
+     *   'barney':  { 'age': 36, 'active': true },
+     *   'fred':    { 'age': 40, 'active': false },
+     *   'pebbles': { 'age': 1,  'active': true }
+     * };
+     *
+     * _.findLastKey(users, function(o) { return o.age < 40; });
+     * // => returns 'pebbles' assuming `_.findKey` returns 'barney'
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.findLastKey(users, { 'age': 36, 'active': true });
+     * // => 'barney'
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.findLastKey(users, ['active', false]);
+     * // => 'fred'
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.findLastKey(users, 'active');
+     * // => 'pebbles'
+     */
+    function findLastKey(object, predicate) {
+      return baseFind(object, getIteratee(predicate, 3), baseForOwnRight, true);
+    }
+
+    /**
+     * Iterates over own and inherited enumerable string keyed properties of an
+     * object and invokes `iteratee` for each property. The iteratee is invoked
+     * with three arguments: (value, key, object). Iteratee functions may exit
+     * iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forInRight
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forIn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a', 'b', then 'c' (iteration order is not guaranteed).
+     */
+    function forIn(object, iteratee) {
+      return object == null
+        ? object
+        : baseFor(object, getIteratee(iteratee), keysIn);
+    }
+
+    /**
+     * This method is like `_.forIn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forInRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'c', 'b', then 'a' assuming `_.forIn` logs 'a', 'b', then 'c'.
+     */
+    function forInRight(object, iteratee) {
+      return object == null
+        ? object
+        : baseForRight(object, getIteratee(iteratee), keysIn);
+    }
+
+    /**
+     * Iterates over own enumerable string keyed properties of an object and
+     * invokes `iteratee` for each property. The iteratee is invoked with three
+     * arguments: (value, key, object). Iteratee functions may exit iteration
+     * early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.3.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forOwnRight
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwn(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'a' then 'b' (iteration order is not guaranteed).
+     */
+    function forOwn(object, iteratee) {
+      return object && baseForOwn(object, getIteratee(iteratee));
+    }
+
+    /**
+     * This method is like `_.forOwn` except that it iterates over properties of
+     * `object` in the opposite order.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.0.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Object} Returns `object`.
+     * @see _.forOwn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.forOwnRight(new Foo, function(value, key) {
+     *   console.log(key);
+     * });
+     * // => Logs 'b' then 'a' assuming `_.forOwn` logs 'a' then 'b'.
+     */
+    function forOwnRight(object, iteratee) {
+      return object && baseForOwnRight(object, getIteratee(iteratee));
+    }
+
+    /**
+     * Creates an array of function property names from own enumerable properties
+     * of `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the new array of property names.
+     * @see _.functionsIn
+     * @example
+     *
+     * function Foo() {
+     *   this.a = _.constant('a');
+     *   this.b = _.constant('b');
+     * }
+     *
+     * Foo.prototype.c = _.constant('c');
+     *
+     * _.functions(new Foo);
+     * // => ['a', 'b']
+     */
+    function functions(object) {
+      return object == null ? [] : baseFunctions(object, keys(object));
+    }
+
+    /**
+     * Creates an array of function property names from own and inherited
+     * enumerable properties of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to inspect.
+     * @returns {Array} Returns the new array of property names.
+     * @see _.functions
+     * @example
+     *
+     * function Foo() {
+     *   this.a = _.constant('a');
+     *   this.b = _.constant('b');
+     * }
+     *
+     * Foo.prototype.c = _.constant('c');
+     *
+     * _.functionsIn(new Foo);
+     * // => ['a', 'b', 'c']
+     */
+    function functionsIn(object) {
+      return object == null ? [] : baseFunctions(object, keysIn(object));
+    }
+
+    /**
+     * Gets the value at `path` of `object`. If the resolved value is
+     * `undefined`, the `defaultValue` is used in its place.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.get(object, 'a[0].b.c');
+     * // => 3
+     *
+     * _.get(object, ['a', '0', 'b', 'c']);
+     * // => 3
+     *
+     * _.get(object, 'a.b.c', 'default');
+     * // => 'default'
+     */
+    function get(object, path, defaultValue) {
+      var result = object == null ? undefined : baseGet(object, path);
+      return result === undefined ? defaultValue : result;
+    }
+
+    /**
+     * Checks if `path` is a direct property of `object`.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     * @example
+     *
+     * var object = { 'a': { 'b': 2 } };
+     * var other = _.create({ 'a': _.create({ 'b': 2 }) });
+     *
+     * _.has(object, 'a');
+     * // => true
+     *
+     * _.has(object, 'a.b');
+     * // => true
+     *
+     * _.has(object, ['a', 'b']);
+     * // => true
+     *
+     * _.has(other, 'a');
+     * // => false
+     */
+    function has(object, path) {
+      return object != null && hasPath(object, path, baseHas);
+    }
+
+    /**
+     * Checks if `path` is a direct or inherited property of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path to check.
+     * @returns {boolean} Returns `true` if `path` exists, else `false`.
+     * @example
+     *
+     * var object = _.create({ 'a': _.create({ 'b': 2 }) });
+     *
+     * _.hasIn(object, 'a');
+     * // => true
+     *
+     * _.hasIn(object, 'a.b');
+     * // => true
+     *
+     * _.hasIn(object, ['a', 'b']);
+     * // => true
+     *
+     * _.hasIn(object, 'b');
+     * // => false
+     */
+    function hasIn(object, path) {
+      return object != null && hasPath(object, path, baseHasIn);
+    }
+
+    /**
+     * Creates an object composed of the inverted keys and values of `object`.
+     * If `object` contains duplicate values, subsequent values overwrite
+     * property assignments of previous values.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.7.0
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invert(object);
+     * // => { '1': 'c', '2': 'b' }
+     */
+    var invert = createInverter(function(result, value, key) {
+      result[value] = key;
+    }, constant(identity));
+
+    /**
+     * This method is like `_.invert` except that the inverted object is generated
+     * from the results of running each element of `object` thru `iteratee`. The
+     * corresponding inverted value of each inverted key is an array of keys
+     * responsible for generating the inverted value. The iteratee is invoked
+     * with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.1.0
+     * @category Object
+     * @param {Object} object The object to invert.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {Object} Returns the new inverted object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': 2, 'c': 1 };
+     *
+     * _.invertBy(object);
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     *
+     * _.invertBy(object, function(value) {
+     *   return 'group' + value;
+     * });
+     * // => { 'group1': ['a', 'c'], 'group2': ['b'] }
+     */
+    var invertBy = createInverter(function(result, value, key) {
+      if (hasOwnProperty.call(result, value)) {
+        result[value].push(key);
+      } else {
+        result[value] = [key];
+      }
+    }, getIteratee);
+
+    /**
+     * Invokes the method at `path` of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {*} Returns the result of the invoked method.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': [1, 2, 3, 4] } }] };
+     *
+     * _.invoke(object, 'a[0].b.c.slice', 1, 3);
+     * // => [2, 3]
+     */
+    var invoke = rest(baseInvoke);
+
+    /**
+     * Creates an array of the own enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects. See the
+     * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys)
+     * for more details.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keys(new Foo);
+     * // => ['a', 'b'] (iteration order is not guaranteed)
+     *
+     * _.keys('hi');
+     * // => ['0', '1']
+     */
+    function keys(object) {
+      var isProto = isPrototype(object);
+      if (!(isProto || isArrayLike(object))) {
+        return baseKeys(object);
+      }
+      var indexes = indexKeys(object),
+          skipIndexes = !!indexes,
+          result = indexes || [],
+          length = result.length;
+
+      for (var key in object) {
+        if (baseHas(object, key) &&
+            !(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+            !(isProto && key == 'constructor')) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable property names of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property names.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.keysIn(new Foo);
+     * // => ['a', 'b', 'c'] (iteration order is not guaranteed)
+     */
+    function keysIn(object) {
+      var index = -1,
+          isProto = isPrototype(object),
+          props = baseKeysIn(object),
+          propsLength = props.length,
+          indexes = indexKeys(object),
+          skipIndexes = !!indexes,
+          result = indexes || [],
+          length = result.length;
+
+      while (++index < propsLength) {
+        var key = props[index];
+        if (!(skipIndexes && (key == 'length' || isIndex(key, length))) &&
+            !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) {
+          result.push(key);
+        }
+      }
+      return result;
+    }
+
+    /**
+     * The opposite of `_.mapValues`; this method creates an object with the
+     * same values as `object` and keys generated by running each own enumerable
+     * string keyed property of `object` thru `iteratee`. The iteratee is invoked
+     * with three arguments: (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.8.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Object} Returns the new mapped object.
+     * @see _.mapValues
+     * @example
+     *
+     * _.mapKeys({ 'a': 1, 'b': 2 }, function(value, key) {
+     *   return key + value;
+     * });
+     * // => { 'a1': 1, 'b2': 2 }
+     */
+    function mapKeys(object, iteratee) {
+      var result = {};
+      iteratee = getIteratee(iteratee, 3);
+
+      baseForOwn(object, function(value, key, object) {
+        result[iteratee(value, key, object)] = value;
+      });
+      return result;
+    }
+
+    /**
+     * Creates an object with the same keys as `object` and values generated
+     * by running each own enumerable string keyed property of `object` thru
+     * `iteratee`. The iteratee is invoked with three arguments:
+     * (value, key, object).
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Object
+     * @param {Object} object The object to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The function invoked per iteration.
+     * @returns {Object} Returns the new mapped object.
+     * @see _.mapKeys
+     * @example
+     *
+     * var users = {
+     *   'fred':    { 'user': 'fred',    'age': 40 },
+     *   'pebbles': { 'user': 'pebbles', 'age': 1 }
+     * };
+     *
+     * _.mapValues(users, function(o) { return o.age; });
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.mapValues(users, 'age');
+     * // => { 'fred': 40, 'pebbles': 1 } (iteration order is not guaranteed)
+     */
+    function mapValues(object, iteratee) {
+      var result = {};
+      iteratee = getIteratee(iteratee, 3);
+
+      baseForOwn(object, function(value, key, object) {
+        result[key] = iteratee(value, key, object);
+      });
+      return result;
+    }
+
+    /**
+     * This method is like `_.assign` except that it recursively merges own and
+     * inherited enumerable string keyed properties of source objects into the
+     * destination object. Source properties that resolve to `undefined` are
+     * skipped if a destination value exists. Array and plain object properties
+     * are merged recursively.Other objects and value types are overridden by
+     * assignment. Source objects are applied from left to right. Subsequent
+     * sources overwrite property assignments of previous sources.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.5.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} [sources] The source objects.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var users = {
+     *   'data': [{ 'user': 'barney' }, { 'user': 'fred' }]
+     * };
+     *
+     * var ages = {
+     *   'data': [{ 'age': 36 }, { 'age': 40 }]
+     * };
+     *
+     * _.merge(users, ages);
+     * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] }
+     */
+    var merge = createAssigner(function(object, source, srcIndex) {
+      baseMerge(object, source, srcIndex);
+    });
+
+    /**
+     * This method is like `_.merge` except that it accepts `customizer` which
+     * is invoked to produce the merged values of the destination and source
+     * properties. If `customizer` returns `undefined`, merging is handled by the
+     * method instead. The `customizer` is invoked with seven arguments:
+     * (objValue, srcValue, key, object, source, stack).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The destination object.
+     * @param {...Object} sources The source objects.
+     * @param {Function} customizer The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * function customizer(objValue, srcValue) {
+     *   if (_.isArray(objValue)) {
+     *     return objValue.concat(srcValue);
+     *   }
+     * }
+     *
+     * var object = {
+     *   'fruits': ['apple'],
+     *   'vegetables': ['beet']
+     * };
+     *
+     * var other = {
+     *   'fruits': ['banana'],
+     *   'vegetables': ['carrot']
+     * };
+     *
+     * _.mergeWith(object, other, customizer);
+     * // => { 'fruits': ['apple', 'banana'], 'vegetables': ['beet', 'carrot'] }
+     */
+    var mergeWith = createAssigner(function(object, source, srcIndex, customizer) {
+      baseMerge(object, source, srcIndex, customizer);
+    });
+
+    /**
+     * The opposite of `_.pick`; this method creates an object composed of the
+     * own and inherited enumerable string keyed properties of `object` that are
+     * not omitted.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {...(string|string[])} [props] The property identifiers to omit.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.omit(object, ['a', 'c']);
+     * // => { 'b': '2' }
+     */
+    var omit = rest(function(object, props) {
+      if (object == null) {
+        return {};
+      }
+      props = arrayMap(baseFlatten(props, 1), toKey);
+      return basePick(object, baseDifference(getAllKeysIn(object), props));
+    });
+
+    /**
+     * The opposite of `_.pickBy`; this method creates an object composed of
+     * the own and inherited enumerable string keyed properties of `object` that
+     * `predicate` doesn't return truthy for. The predicate is invoked with two
+     * arguments: (value, key).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per property.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.omitBy(object, _.isNumber);
+     * // => { 'b': '2' }
+     */
+    function omitBy(object, predicate) {
+      predicate = getIteratee(predicate);
+      return basePickBy(object, function(value, key) {
+        return !predicate(value, key);
+      });
+    }
+
+    /**
+     * Creates an object composed of the picked `object` properties.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {...(string|string[])} [props] The property identifiers to pick.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.pick(object, ['a', 'c']);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    var pick = rest(function(object, props) {
+      return object == null ? {} : basePick(object, arrayMap(baseFlatten(props, 1), toKey));
+    });
+
+    /**
+     * Creates an object composed of the `object` properties `predicate` returns
+     * truthy for. The predicate is invoked with two arguments: (value, key).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The source object.
+     * @param {Array|Function|Object|string} [predicate=_.identity]
+     *  The function invoked per property.
+     * @returns {Object} Returns the new object.
+     * @example
+     *
+     * var object = { 'a': 1, 'b': '2', 'c': 3 };
+     *
+     * _.pickBy(object, _.isNumber);
+     * // => { 'a': 1, 'c': 3 }
+     */
+    function pickBy(object, predicate) {
+      return object == null ? {} : basePickBy(object, getIteratee(predicate));
+    }
+
+    /**
+     * This method is like `_.get` except that if the resolved value is a
+     * function it's invoked with the `this` binding of its parent object and
+     * its result is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @param {Array|string} path The path of the property to resolve.
+     * @param {*} [defaultValue] The value returned for `undefined` resolved values.
+     * @returns {*} Returns the resolved value.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c1': 3, 'c2': _.constant(4) } }] };
+     *
+     * _.result(object, 'a[0].b.c1');
+     * // => 3
+     *
+     * _.result(object, 'a[0].b.c2');
+     * // => 4
+     *
+     * _.result(object, 'a[0].b.c3', 'default');
+     * // => 'default'
+     *
+     * _.result(object, 'a[0].b.c3', _.constant('default'));
+     * // => 'default'
+     */
+    function result(object, path, defaultValue) {
+      path = isKey(path, object) ? [path] : castPath(path);
+
+      var index = -1,
+          length = path.length;
+
+      // Ensure the loop is entered when path is empty.
+      if (!length) {
+        object = undefined;
+        length = 1;
+      }
+      while (++index < length) {
+        var value = object == null ? undefined : object[toKey(path[index])];
+        if (value === undefined) {
+          index = length;
+          value = defaultValue;
+        }
+        object = isFunction(value) ? value.call(object) : value;
+      }
+      return object;
+    }
+
+    /**
+     * Sets the value at `path` of `object`. If a portion of `path` doesn't exist,
+     * it's created. Arrays are created for missing index properties while objects
+     * are created for all other missing properties. Use `_.setWith` to customize
+     * `path` creation.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.set(object, 'a[0].b.c', 4);
+     * console.log(object.a[0].b.c);
+     * // => 4
+     *
+     * _.set(object, ['x', '0', 'y', 'z'], 5);
+     * console.log(object.x[0].y.z);
+     * // => 5
+     */
+    function set(object, path, value) {
+      return object == null ? object : baseSet(object, path, value);
+    }
+
+    /**
+     * This method is like `_.set` except that it accepts `customizer` which is
+     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+     * path creation is handled by the method instead. The `customizer` is invoked
+     * with three arguments: (nsValue, key, nsObject).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {*} value The value to set.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {};
+     *
+     * _.setWith(object, '[0][1]', 'a', Object);
+     * // => { '0': { '1': 'a' } }
+     */
+    function setWith(object, path, value, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return object == null ? object : baseSet(object, path, value, customizer);
+    }
+
+    /**
+     * Creates an array of own enumerable string keyed-value pairs for `object`
+     * which can be consumed by `_.fromPairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias entries
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the new array of key-value pairs.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.toPairs(new Foo);
+     * // => [['a', 1], ['b', 2]] (iteration order is not guaranteed)
+     */
+    function toPairs(object) {
+      return baseToPairs(object, keys(object));
+    }
+
+    /**
+     * Creates an array of own and inherited enumerable string keyed-value pairs
+     * for `object` which can be consumed by `_.fromPairs`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @alias entriesIn
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the new array of key-value pairs.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.toPairsIn(new Foo);
+     * // => [['a', 1], ['b', 2], ['c', 1]] (iteration order is not guaranteed)
+     */
+    function toPairsIn(object) {
+      return baseToPairs(object, keysIn(object));
+    }
+
+    /**
+     * An alternative to `_.reduce`; this method transforms `object` to a new
+     * `accumulator` object which is the result of running each of its own
+     * enumerable string keyed properties thru `iteratee`, with each invocation
+     * potentially mutating the `accumulator` object. The iteratee is invoked
+     * with four arguments: (accumulator, value, key, object). Iteratee functions
+     * may exit iteration early by explicitly returning `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.3.0
+     * @category Object
+     * @param {Array|Object} object The object to iterate over.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @param {*} [accumulator] The custom accumulator value.
+     * @returns {*} Returns the accumulated value.
+     * @example
+     *
+     * _.transform([2, 3, 4], function(result, n) {
+     *   result.push(n *= n);
+     *   return n % 2 == 0;
+     * }, []);
+     * // => [4, 9]
+     *
+     * _.transform({ 'a': 1, 'b': 2, 'c': 1 }, function(result, value, key) {
+     *   (result[value] || (result[value] = [])).push(key);
+     * }, {});
+     * // => { '1': ['a', 'c'], '2': ['b'] }
+     */
+    function transform(object, iteratee, accumulator) {
+      var isArr = isArray(object) || isTypedArray(object);
+      iteratee = getIteratee(iteratee, 4);
+
+      if (accumulator == null) {
+        if (isArr || isObject(object)) {
+          var Ctor = object.constructor;
+          if (isArr) {
+            accumulator = isArray(object) ? new Ctor : [];
+          } else {
+            accumulator = isFunction(Ctor) ? baseCreate(getPrototype(object)) : {};
+          }
+        } else {
+          accumulator = {};
+        }
+      }
+      (isArr ? arrayEach : baseForOwn)(object, function(value, index, object) {
+        return iteratee(accumulator, value, index, object);
+      });
+      return accumulator;
+    }
+
+    /**
+     * Removes the property at `path` of `object`.
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to unset.
+     * @returns {boolean} Returns `true` if the property is deleted, else `false`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 7 } }] };
+     * _.unset(object, 'a[0].b.c');
+     * // => true
+     *
+     * console.log(object);
+     * // => { 'a': [{ 'b': {} }] };
+     *
+     * _.unset(object, ['a', '0', 'b', 'c']);
+     * // => true
+     *
+     * console.log(object);
+     * // => { 'a': [{ 'b': {} }] };
+     */
+    function unset(object, path) {
+      return object == null ? true : baseUnset(object, path);
+    }
+
+    /**
+     * This method is like `_.set` except that accepts `updater` to produce the
+     * value to set. Use `_.updateWith` to customize `path` creation. The `updater`
+     * is invoked with one argument: (value).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {Function} updater The function to produce the updated value.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = { 'a': [{ 'b': { 'c': 3 } }] };
+     *
+     * _.update(object, 'a[0].b.c', function(n) { return n * n; });
+     * console.log(object.a[0].b.c);
+     * // => 9
+     *
+     * _.update(object, 'x[0].y.z', function(n) { return n ? n + 1 : 0; });
+     * console.log(object.x[0].y.z);
+     * // => 0
+     */
+    function update(object, path, updater) {
+      return object == null ? object : baseUpdate(object, path, castFunction(updater));
+    }
+
+    /**
+     * This method is like `_.update` except that it accepts `customizer` which is
+     * invoked to produce the objects of `path`.  If `customizer` returns `undefined`
+     * path creation is handled by the method instead. The `customizer` is invoked
+     * with three arguments: (nsValue, key, nsObject).
+     *
+     * **Note:** This method mutates `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.6.0
+     * @category Object
+     * @param {Object} object The object to modify.
+     * @param {Array|string} path The path of the property to set.
+     * @param {Function} updater The function to produce the updated value.
+     * @param {Function} [customizer] The function to customize assigned values.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var object = {};
+     *
+     * _.updateWith(object, '[0][1]', _.constant('a'), Object);
+     * // => { '0': { '1': 'a' } }
+     */
+    function updateWith(object, path, updater, customizer) {
+      customizer = typeof customizer == 'function' ? customizer : undefined;
+      return object == null ? object : baseUpdate(object, path, castFunction(updater), customizer);
+    }
+
+    /**
+     * Creates an array of the own enumerable string keyed property values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.values(new Foo);
+     * // => [1, 2] (iteration order is not guaranteed)
+     *
+     * _.values('hi');
+     * // => ['h', 'i']
+     */
+    function values(object) {
+      return object ? baseValues(object, keys(object)) : [];
+    }
+
+    /**
+     * Creates an array of the own and inherited enumerable string keyed property
+     * values of `object`.
+     *
+     * **Note:** Non-object values are coerced to objects.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Object
+     * @param {Object} object The object to query.
+     * @returns {Array} Returns the array of property values.
+     * @example
+     *
+     * function Foo() {
+     *   this.a = 1;
+     *   this.b = 2;
+     * }
+     *
+     * Foo.prototype.c = 3;
+     *
+     * _.valuesIn(new Foo);
+     * // => [1, 2, 3] (iteration order is not guaranteed)
+     */
+    function valuesIn(object) {
+      return object == null ? [] : baseValues(object, keysIn(object));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Clamps `number` within the inclusive `lower` and `upper` bounds.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Number
+     * @param {number} number The number to clamp.
+     * @param {number} [lower] The lower bound.
+     * @param {number} upper The upper bound.
+     * @returns {number} Returns the clamped number.
+     * @example
+     *
+     * _.clamp(-10, -5, 5);
+     * // => -5
+     *
+     * _.clamp(10, -5, 5);
+     * // => 5
+     */
+    function clamp(number, lower, upper) {
+      if (upper === undefined) {
+        upper = lower;
+        lower = undefined;
+      }
+      if (upper !== undefined) {
+        upper = toNumber(upper);
+        upper = upper === upper ? upper : 0;
+      }
+      if (lower !== undefined) {
+        lower = toNumber(lower);
+        lower = lower === lower ? lower : 0;
+      }
+      return baseClamp(toNumber(number), lower, upper);
+    }
+
+    /**
+     * Checks if `n` is between `start` and up to, but not including, `end`. If
+     * `end` is not specified, it's set to `start` with `start` then set to `0`.
+     * If `start` is greater than `end` the params are swapped to support
+     * negative ranges.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.3.0
+     * @category Number
+     * @param {number} number The number to check.
+     * @param {number} [start=0] The start of the range.
+     * @param {number} end The end of the range.
+     * @returns {boolean} Returns `true` if `number` is in the range, else `false`.
+     * @see _.range, _.rangeRight
+     * @example
+     *
+     * _.inRange(3, 2, 4);
+     * // => true
+     *
+     * _.inRange(4, 8);
+     * // => true
+     *
+     * _.inRange(4, 2);
+     * // => false
+     *
+     * _.inRange(2, 2);
+     * // => false
+     *
+     * _.inRange(1.2, 2);
+     * // => true
+     *
+     * _.inRange(5.2, 4);
+     * // => false
+     *
+     * _.inRange(-3, -2, -6);
+     * // => true
+     */
+    function inRange(number, start, end) {
+      start = toNumber(start) || 0;
+      if (end === undefined) {
+        end = start;
+        start = 0;
+      } else {
+        end = toNumber(end) || 0;
+      }
+      number = toNumber(number);
+      return baseInRange(number, start, end);
+    }
+
+    /**
+     * Produces a random number between the inclusive `lower` and `upper` bounds.
+     * If only one argument is provided a number between `0` and the given number
+     * is returned. If `floating` is `true`, or either `lower` or `upper` are
+     * floats, a floating-point number is returned instead of an integer.
+     *
+     * **Note:** JavaScript follows the IEEE-754 standard for resolving
+     * floating-point values which can produce unexpected results.
+     *
+     * @static
+     * @memberOf _
+     * @since 0.7.0
+     * @category Number
+     * @param {number} [lower=0] The lower bound.
+     * @param {number} [upper=1] The upper bound.
+     * @param {boolean} [floating] Specify returning a floating-point number.
+     * @returns {number} Returns the random number.
+     * @example
+     *
+     * _.random(0, 5);
+     * // => an integer between 0 and 5
+     *
+     * _.random(5);
+     * // => also an integer between 0 and 5
+     *
+     * _.random(5, true);
+     * // => a floating-point number between 0 and 5
+     *
+     * _.random(1.2, 5.2);
+     * // => a floating-point number between 1.2 and 5.2
+     */
+    function random(lower, upper, floating) {
+      if (floating && typeof floating != 'boolean' && isIterateeCall(lower, upper, floating)) {
+        upper = floating = undefined;
+      }
+      if (floating === undefined) {
+        if (typeof upper == 'boolean') {
+          floating = upper;
+          upper = undefined;
+        }
+        else if (typeof lower == 'boolean') {
+          floating = lower;
+          lower = undefined;
+        }
+      }
+      if (lower === undefined && upper === undefined) {
+        lower = 0;
+        upper = 1;
+      }
+      else {
+        lower = toNumber(lower) || 0;
+        if (upper === undefined) {
+          upper = lower;
+          lower = 0;
+        } else {
+          upper = toNumber(upper) || 0;
+        }
+      }
+      if (lower > upper) {
+        var temp = lower;
+        lower = upper;
+        upper = temp;
+      }
+      if (floating || lower % 1 || upper % 1) {
+        var rand = nativeRandom();
+        return nativeMin(lower + (rand * (upper - lower + freeParseFloat('1e-' + ((rand + '').length - 1)))), upper);
+      }
+      return baseRandom(lower, upper);
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Converts `string` to [camel case](https://en.wikipedia.org/wiki/CamelCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the camel cased string.
+     * @example
+     *
+     * _.camelCase('Foo Bar');
+     * // => 'fooBar'
+     *
+     * _.camelCase('--foo-bar--');
+     * // => 'fooBar'
+     *
+     * _.camelCase('__FOO_BAR__');
+     * // => 'fooBar'
+     */
+    var camelCase = createCompounder(function(result, word, index) {
+      word = word.toLowerCase();
+      return result + (index ? capitalize(word) : word);
+    });
+
+    /**
+     * Converts the first character of `string` to upper case and the remaining
+     * to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to capitalize.
+     * @returns {string} Returns the capitalized string.
+     * @example
+     *
+     * _.capitalize('FRED');
+     * // => 'Fred'
+     */
+    function capitalize(string) {
+      return upperFirst(toString(string).toLowerCase());
+    }
+
+    /**
+     * Deburrs `string` by converting
+     * [latin-1 supplementary letters](https://en.wikipedia.org/wiki/Latin-1_Supplement_(Unicode_block)#Character_table)
+     * to basic latin letters and removing
+     * [combining diacritical marks](https://en.wikipedia.org/wiki/Combining_Diacritical_Marks).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to deburr.
+     * @returns {string} Returns the deburred string.
+     * @example
+     *
+     * _.deburr('déjà vu');
+     * // => 'deja vu'
+     */
+    function deburr(string) {
+      string = toString(string);
+      return string && string.replace(reLatin1, deburrLetter).replace(reComboMark, '');
+    }
+
+    /**
+     * Checks if `string` ends with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to search.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=string.length] The position to search from.
+     * @returns {boolean} Returns `true` if `string` ends with `target`,
+     *  else `false`.
+     * @example
+     *
+     * _.endsWith('abc', 'c');
+     * // => true
+     *
+     * _.endsWith('abc', 'b');
+     * // => false
+     *
+     * _.endsWith('abc', 'b', 2);
+     * // => true
+     */
+    function endsWith(string, target, position) {
+      string = toString(string);
+      target = baseToString(target);
+
+      var length = string.length;
+      position = position === undefined
+        ? length
+        : baseClamp(toInteger(position), 0, length);
+
+      position -= target.length;
+      return position >= 0 && string.indexOf(target, position) == position;
+    }
+
+    /**
+     * Converts the characters "&", "<", ">", '"', "'", and "\`" in `string` to
+     * their corresponding HTML entities.
+     *
+     * **Note:** No other characters are escaped. To escape additional
+     * characters use a third-party library like [_he_](https://mths.be/he).
+     *
+     * Though the ">" character is escaped for symmetry, characters like
+     * ">" and "/" don't need escaping in HTML and have no special meaning
+     * unless they're part of a tag or unquoted attribute value. See
+     * [Mathias Bynens's article](https://mathiasbynens.be/notes/ambiguous-ampersands)
+     * (under "semi-related fun fact") for more details.
+     *
+     * Backticks are escaped because in IE < 9, they can break out of
+     * attribute values or HTML comments. See [#59](https://html5sec.org/#59),
+     * [#102](https://html5sec.org/#102), [#108](https://html5sec.org/#108), and
+     * [#133](https://html5sec.org/#133) of the
+     * [HTML5 Security Cheatsheet](https://html5sec.org/) for more details.
+     *
+     * When working with HTML you should always
+     * [quote attribute values](http://wonko.com/post/html-escaping) to reduce
+     * XSS vectors.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escape('fred, barney, & pebbles');
+     * // => 'fred, barney, &amp; pebbles'
+     */
+    function escape(string) {
+      string = toString(string);
+      return (string && reHasUnescapedHtml.test(string))
+        ? string.replace(reUnescapedHtml, escapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+",
+     * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to escape.
+     * @returns {string} Returns the escaped string.
+     * @example
+     *
+     * _.escapeRegExp('[lodash](https://lodash.com/)');
+     * // => '\[lodash\]\(https://lodash\.com/\)'
+     */
+    function escapeRegExp(string) {
+      string = toString(string);
+      return (string && reHasRegExpChar.test(string))
+        ? string.replace(reRegExpChar, '\\$&')
+        : string;
+    }
+
+    /**
+     * Converts `string` to
+     * [kebab case](https://en.wikipedia.org/wiki/Letter_case#Special_case_styles).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the kebab cased string.
+     * @example
+     *
+     * _.kebabCase('Foo Bar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('fooBar');
+     * // => 'foo-bar'
+     *
+     * _.kebabCase('__FOO_BAR__');
+     * // => 'foo-bar'
+     */
+    var kebabCase = createCompounder(function(result, word, index) {
+      return result + (index ? '-' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts `string`, as space separated words, to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the lower cased string.
+     * @example
+     *
+     * _.lowerCase('--Foo-Bar--');
+     * // => 'foo bar'
+     *
+     * _.lowerCase('fooBar');
+     * // => 'foo bar'
+     *
+     * _.lowerCase('__FOO_BAR__');
+     * // => 'foo bar'
+     */
+    var lowerCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Converts the first character of `string` to lower case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.lowerFirst('Fred');
+     * // => 'fred'
+     *
+     * _.lowerFirst('FRED');
+     * // => 'fRED'
+     */
+    var lowerFirst = createCaseFirst('toLowerCase');
+
+    /**
+     * Pads `string` on the left and right sides if it's shorter than `length`.
+     * Padding characters are truncated if they can't be evenly divided by `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.pad('abc', 8);
+     * // => '  abc   '
+     *
+     * _.pad('abc', 8, '_-');
+     * // => '_-abc_-_'
+     *
+     * _.pad('abc', 3);
+     * // => 'abc'
+     */
+    function pad(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      if (!length || strLength >= length) {
+        return string;
+      }
+      var mid = (length - strLength) / 2;
+      return (
+        createPadding(nativeFloor(mid), chars) +
+        string +
+        createPadding(nativeCeil(mid), chars)
+      );
+    }
+
+    /**
+     * Pads `string` on the right side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padEnd('abc', 6);
+     * // => 'abc   '
+     *
+     * _.padEnd('abc', 6, '_-');
+     * // => 'abc_-_'
+     *
+     * _.padEnd('abc', 3);
+     * // => 'abc'
+     */
+    function padEnd(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      return (length && strLength < length)
+        ? (string + createPadding(length - strLength, chars))
+        : string;
+    }
+
+    /**
+     * Pads `string` on the left side if it's shorter than `length`. Padding
+     * characters are truncated if they exceed `length`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to pad.
+     * @param {number} [length=0] The padding length.
+     * @param {string} [chars=' '] The string used as padding.
+     * @returns {string} Returns the padded string.
+     * @example
+     *
+     * _.padStart('abc', 6);
+     * // => '   abc'
+     *
+     * _.padStart('abc', 6, '_-');
+     * // => '_-_abc'
+     *
+     * _.padStart('abc', 3);
+     * // => 'abc'
+     */
+    function padStart(string, length, chars) {
+      string = toString(string);
+      length = toInteger(length);
+
+      var strLength = length ? stringSize(string) : 0;
+      return (length && strLength < length)
+        ? (createPadding(length - strLength, chars) + string)
+        : string;
+    }
+
+    /**
+     * Converts `string` to an integer of the specified radix. If `radix` is
+     * `undefined` or `0`, a `radix` of `10` is used unless `value` is a
+     * hexadecimal, in which case a `radix` of `16` is used.
+     *
+     * **Note:** This method aligns with the
+     * [ES5 implementation](https://es5.github.io/#x15.1.2.2) of `parseInt`.
+     *
+     * @static
+     * @memberOf _
+     * @since 1.1.0
+     * @category String
+     * @param {string} string The string to convert.
+     * @param {number} [radix=10] The radix to interpret `value` by.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {number} Returns the converted integer.
+     * @example
+     *
+     * _.parseInt('08');
+     * // => 8
+     *
+     * _.map(['6', '08', '10'], _.parseInt);
+     * // => [6, 8, 10]
+     */
+    function parseInt(string, radix, guard) {
+      // Chrome fails to trim leading <BOM> whitespace characters.
+      // See https://bugs.chromium.org/p/v8/issues/detail?id=3109 for more details.
+      if (guard || radix == null) {
+        radix = 0;
+      } else if (radix) {
+        radix = +radix;
+      }
+      string = toString(string).replace(reTrim, '');
+      return nativeParseInt(string, radix || (reHasHexPrefix.test(string) ? 16 : 10));
+    }
+
+    /**
+     * Repeats the given string `n` times.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to repeat.
+     * @param {number} [n=1] The number of times to repeat the string.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the repeated string.
+     * @example
+     *
+     * _.repeat('*', 3);
+     * // => '***'
+     *
+     * _.repeat('abc', 2);
+     * // => 'abcabc'
+     *
+     * _.repeat('abc', 0);
+     * // => ''
+     */
+    function repeat(string, n, guard) {
+      if ((guard ? isIterateeCall(string, n, guard) : n === undefined)) {
+        n = 1;
+      } else {
+        n = toInteger(n);
+      }
+      return baseRepeat(toString(string), n);
+    }
+
+    /**
+     * Replaces matches for `pattern` in `string` with `replacement`.
+     *
+     * **Note:** This method is based on
+     * [`String#replace`](https://mdn.io/String/replace).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to modify.
+     * @param {RegExp|string} pattern The pattern to replace.
+     * @param {Function|string} replacement The match replacement.
+     * @returns {string} Returns the modified string.
+     * @example
+     *
+     * _.replace('Hi Fred', 'Fred', 'Barney');
+     * // => 'Hi Barney'
+     */
+    function replace() {
+      var args = arguments,
+          string = toString(args[0]);
+
+      return args.length < 3 ? string : nativeReplace.call(string, args[1], args[2]);
+    }
+
+    /**
+     * Converts `string` to
+     * [snake case](https://en.wikipedia.org/wiki/Snake_case).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the snake cased string.
+     * @example
+     *
+     * _.snakeCase('Foo Bar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('fooBar');
+     * // => 'foo_bar'
+     *
+     * _.snakeCase('--FOO-BAR--');
+     * // => 'foo_bar'
+     */
+    var snakeCase = createCompounder(function(result, word, index) {
+      return result + (index ? '_' : '') + word.toLowerCase();
+    });
+
+    /**
+     * Splits `string` by `separator`.
+     *
+     * **Note:** This method is based on
+     * [`String#split`](https://mdn.io/String/split).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to split.
+     * @param {RegExp|string} separator The separator pattern to split by.
+     * @param {number} [limit] The length to truncate results to.
+     * @returns {Array} Returns the new array of string segments.
+     * @example
+     *
+     * _.split('a-b-c', '-', 2);
+     * // => ['a', 'b']
+     */
+    function split(string, separator, limit) {
+      if (limit && typeof limit != 'number' && isIterateeCall(string, separator, limit)) {
+        separator = limit = undefined;
+      }
+      limit = limit === undefined ? MAX_ARRAY_LENGTH : limit >>> 0;
+      if (!limit) {
+        return [];
+      }
+      string = toString(string);
+      if (string && (
+            typeof separator == 'string' ||
+            (separator != null && !isRegExp(separator))
+          )) {
+        separator = baseToString(separator);
+        if (separator == '' && reHasComplexSymbol.test(string)) {
+          return castSlice(stringToArray(string), 0, limit);
+        }
+      }
+      return nativeSplit.call(string, separator, limit);
+    }
+
+    /**
+     * Converts `string` to
+     * [start case](https://en.wikipedia.org/wiki/Letter_case#Stylistic_or_specialised_usage).
+     *
+     * @static
+     * @memberOf _
+     * @since 3.1.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the start cased string.
+     * @example
+     *
+     * _.startCase('--foo-bar--');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('fooBar');
+     * // => 'Foo Bar'
+     *
+     * _.startCase('__FOO_BAR__');
+     * // => 'FOO BAR'
+     */
+    var startCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + upperFirst(word);
+    });
+
+    /**
+     * Checks if `string` starts with the given target string.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to search.
+     * @param {string} [target] The string to search for.
+     * @param {number} [position=0] The position to search from.
+     * @returns {boolean} Returns `true` if `string` starts with `target`,
+     *  else `false`.
+     * @example
+     *
+     * _.startsWith('abc', 'a');
+     * // => true
+     *
+     * _.startsWith('abc', 'b');
+     * // => false
+     *
+     * _.startsWith('abc', 'b', 1);
+     * // => true
+     */
+    function startsWith(string, target, position) {
+      string = toString(string);
+      position = baseClamp(toInteger(position), 0, string.length);
+      return string.lastIndexOf(baseToString(target), position) == position;
+    }
+
+    /**
+     * Creates a compiled template function that can interpolate data properties
+     * in "interpolate" delimiters, HTML-escape interpolated data properties in
+     * "escape" delimiters, and execute JavaScript in "evaluate" delimiters. Data
+     * properties may be accessed as free variables in the template. If a setting
+     * object is given, it takes precedence over `_.templateSettings` values.
+     *
+     * **Note:** In the development build `_.template` utilizes
+     * [sourceURLs](http://www.html5rocks.com/en/tutorials/developertools/sourcemaps/#toc-sourceurl)
+     * for easier debugging.
+     *
+     * For more information on precompiling templates see
+     * [lodash's custom builds documentation](https://lodash.com/custom-builds).
+     *
+     * For more information on Chrome extension sandboxes see
+     * [Chrome's extensions documentation](https://developer.chrome.com/extensions/sandboxingEval).
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category String
+     * @param {string} [string=''] The template string.
+     * @param {Object} [options={}] The options object.
+     * @param {RegExp} [options.escape=_.templateSettings.escape]
+     *  The HTML "escape" delimiter.
+     * @param {RegExp} [options.evaluate=_.templateSettings.evaluate]
+     *  The "evaluate" delimiter.
+     * @param {Object} [options.imports=_.templateSettings.imports]
+     *  An object to import into the template as free variables.
+     * @param {RegExp} [options.interpolate=_.templateSettings.interpolate]
+     *  The "interpolate" delimiter.
+     * @param {string} [options.sourceURL='lodash.templateSources[n]']
+     *  The sourceURL of the compiled template.
+     * @param {string} [options.variable='obj']
+     *  The data object variable name.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Function} Returns the compiled template function.
+     * @example
+     *
+     * // Use the "interpolate" delimiter to create a compiled template.
+     * var compiled = _.template('hello <%= user %>!');
+     * compiled({ 'user': 'fred' });
+     * // => 'hello fred!'
+     *
+     * // Use the HTML "escape" delimiter to escape data property values.
+     * var compiled = _.template('<b><%- value %></b>');
+     * compiled({ 'value': '<script>' });
+     * // => '<b>&lt;script&gt;</b>'
+     *
+     * // Use the "evaluate" delimiter to execute JavaScript and generate HTML.
+     * var compiled = _.template('<% _.forEach(users, function(user) { %><li><%- user %></li><% }); %>');
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // Use the internal `print` function in "evaluate" delimiters.
+     * var compiled = _.template('<% print("hello " + user); %>!');
+     * compiled({ 'user': 'barney' });
+     * // => 'hello barney!'
+     *
+     * // Use the ES delimiter as an alternative to the default "interpolate" delimiter.
+     * var compiled = _.template('hello ${ user }!');
+     * compiled({ 'user': 'pebbles' });
+     * // => 'hello pebbles!'
+     *
+     * // Use custom template delimiters.
+     * _.templateSettings.interpolate = /{{([\s\S]+?)}}/g;
+     * var compiled = _.template('hello {{ user }}!');
+     * compiled({ 'user': 'mustache' });
+     * // => 'hello mustache!'
+     *
+     * // Use backslashes to treat delimiters as plain text.
+     * var compiled = _.template('<%= "\\<%- value %\\>" %>');
+     * compiled({ 'value': 'ignored' });
+     * // => '<%- value %>'
+     *
+     * // Use the `imports` option to import `jQuery` as `jq`.
+     * var text = '<% jq.each(users, function(user) { %><li><%- user %></li><% }); %>';
+     * var compiled = _.template(text, { 'imports': { 'jq': jQuery } });
+     * compiled({ 'users': ['fred', 'barney'] });
+     * // => '<li>fred</li><li>barney</li>'
+     *
+     * // Use the `sourceURL` option to specify a custom sourceURL for the template.
+     * var compiled = _.template('hello <%= user %>!', { 'sourceURL': '/basic/greeting.jst' });
+     * compiled(data);
+     * // => Find the source of "greeting.jst" under the Sources tab or Resources panel of the web inspector.
+     *
+     * // Use the `variable` option to ensure a with-statement isn't used in the compiled template.
+     * var compiled = _.template('hi <%= data.user %>!', { 'variable': 'data' });
+     * compiled.source;
+     * // => function(data) {
+     * //   var __t, __p = '';
+     * //   __p += 'hi ' + ((__t = ( data.user )) == null ? '' : __t) + '!';
+     * //   return __p;
+     * // }
+     *
+     * // Use the `source` property to inline compiled templates for meaningful
+     * // line numbers in error messages and stack traces.
+     * fs.writeFileSync(path.join(cwd, 'jst.js'), '\
+     *   var JST = {\
+     *     "main": ' + _.template(mainText).source + '\
+     *   };\
+     * ');
+     */
+    function template(string, options, guard) {
+      // Based on John Resig's `tmpl` implementation
+      // (http://ejohn.org/blog/javascript-micro-templating/)
+      // and Laura Doktorova's doT.js (https://github.com/olado/doT).
+      var settings = lodash.templateSettings;
+
+      if (guard && isIterateeCall(string, options, guard)) {
+        options = undefined;
+      }
+      string = toString(string);
+      options = assignInWith({}, options, settings, assignInDefaults);
+
+      var imports = assignInWith({}, options.imports, settings.imports, assignInDefaults),
+          importsKeys = keys(imports),
+          importsValues = baseValues(imports, importsKeys);
+
+      var isEscaping,
+          isEvaluating,
+          index = 0,
+          interpolate = options.interpolate || reNoMatch,
+          source = "__p += '";
+
+      // Compile the regexp to match each delimiter.
+      var reDelimiters = RegExp(
+        (options.escape || reNoMatch).source + '|' +
+        interpolate.source + '|' +
+        (interpolate === reInterpolate ? reEsTemplate : reNoMatch).source + '|' +
+        (options.evaluate || reNoMatch).source + '|$'
+      , 'g');
+
+      // Use a sourceURL for easier debugging.
+      var sourceURL = '//# sourceURL=' +
+        ('sourceURL' in options
+          ? options.sourceURL
+          : ('lodash.templateSources[' + (++templateCounter) + ']')
+        ) + '\n';
+
+      string.replace(reDelimiters, function(match, escapeValue, interpolateValue, esTemplateValue, evaluateValue, offset) {
+        interpolateValue || (interpolateValue = esTemplateValue);
+
+        // Escape characters that can't be included in string literals.
+        source += string.slice(index, offset).replace(reUnescapedString, escapeStringChar);
+
+        // Replace delimiters with snippets.
+        if (escapeValue) {
+          isEscaping = true;
+          source += "' +\n__e(" + escapeValue + ") +\n'";
+        }
+        if (evaluateValue) {
+          isEvaluating = true;
+          source += "';\n" + evaluateValue + ";\n__p += '";
+        }
+        if (interpolateValue) {
+          source += "' +\n((__t = (" + interpolateValue + ")) == null ? '' : __t) +\n'";
+        }
+        index = offset + match.length;
+
+        // The JS engine embedded in Adobe products needs `match` returned in
+        // order to produce the correct `offset` value.
+        return match;
+      });
+
+      source += "';\n";
+
+      // If `variable` is not specified wrap a with-statement around the generated
+      // code to add the data object to the top of the scope chain.
+      var variable = options.variable;
+      if (!variable) {
+        source = 'with (obj) {\n' + source + '\n}\n';
+      }
+      // Cleanup code by stripping empty strings.
+      source = (isEvaluating ? source.replace(reEmptyStringLeading, '') : source)
+        .replace(reEmptyStringMiddle, '$1')
+        .replace(reEmptyStringTrailing, '$1;');
+
+      // Frame code as the function body.
+      source = 'function(' + (variable || 'obj') + ') {\n' +
+        (variable
+          ? ''
+          : 'obj || (obj = {});\n'
+        ) +
+        "var __t, __p = ''" +
+        (isEscaping
+           ? ', __e = _.escape'
+           : ''
+        ) +
+        (isEvaluating
+          ? ', __j = Array.prototype.join;\n' +
+            "function print() { __p += __j.call(arguments, '') }\n"
+          : ';\n'
+        ) +
+        source +
+        'return __p\n}';
+
+      var result = attempt(function() {
+        return Function(importsKeys, sourceURL + 'return ' + source)
+          .apply(undefined, importsValues);
+      });
+
+      // Provide the compiled function's source by its `toString` method or
+      // the `source` property as a convenience for inlining compiled templates.
+      result.source = source;
+      if (isError(result)) {
+        throw result;
+      }
+      return result;
+    }
+
+    /**
+     * Converts `string`, as a whole, to lower case just like
+     * [String#toLowerCase](https://mdn.io/toLowerCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the lower cased string.
+     * @example
+     *
+     * _.toLower('--Foo-Bar--');
+     * // => '--foo-bar--'
+     *
+     * _.toLower('fooBar');
+     * // => 'foobar'
+     *
+     * _.toLower('__FOO_BAR__');
+     * // => '__foo_bar__'
+     */
+    function toLower(value) {
+      return toString(value).toLowerCase();
+    }
+
+    /**
+     * Converts `string`, as a whole, to upper case just like
+     * [String#toUpperCase](https://mdn.io/toUpperCase).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the upper cased string.
+     * @example
+     *
+     * _.toUpper('--foo-bar--');
+     * // => '--FOO-BAR--'
+     *
+     * _.toUpper('fooBar');
+     * // => 'FOOBAR'
+     *
+     * _.toUpper('__foo_bar__');
+     * // => '__FOO_BAR__'
+     */
+    function toUpper(value) {
+      return toString(value).toUpperCase();
+    }
+
+    /**
+     * Removes leading and trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trim('  abc  ');
+     * // => 'abc'
+     *
+     * _.trim('-_-abc-_-', '_-');
+     * // => 'abc'
+     *
+     * _.map(['  foo  ', '  bar  '], _.trim);
+     * // => ['foo', 'bar']
+     */
+    function trim(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrim, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          chrSymbols = stringToArray(chars),
+          start = charsStartIndex(strSymbols, chrSymbols),
+          end = charsEndIndex(strSymbols, chrSymbols) + 1;
+
+      return castSlice(strSymbols, start, end).join('');
+    }
+
+    /**
+     * Removes trailing whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimEnd('  abc  ');
+     * // => '  abc'
+     *
+     * _.trimEnd('-_-abc-_-', '_-');
+     * // => '-_-abc'
+     */
+    function trimEnd(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrimEnd, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          end = charsEndIndex(strSymbols, stringToArray(chars)) + 1;
+
+      return castSlice(strSymbols, 0, end).join('');
+    }
+
+    /**
+     * Removes leading whitespace or specified characters from `string`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to trim.
+     * @param {string} [chars=whitespace] The characters to trim.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {string} Returns the trimmed string.
+     * @example
+     *
+     * _.trimStart('  abc  ');
+     * // => 'abc  '
+     *
+     * _.trimStart('-_-abc-_-', '_-');
+     * // => 'abc-_-'
+     */
+    function trimStart(string, chars, guard) {
+      string = toString(string);
+      if (string && (guard || chars === undefined)) {
+        return string.replace(reTrimStart, '');
+      }
+      if (!string || !(chars = baseToString(chars))) {
+        return string;
+      }
+      var strSymbols = stringToArray(string),
+          start = charsStartIndex(strSymbols, stringToArray(chars));
+
+      return castSlice(strSymbols, start).join('');
+    }
+
+    /**
+     * Truncates `string` if it's longer than the given maximum string length.
+     * The last characters of the truncated string are replaced with the omission
+     * string which defaults to "...".
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to truncate.
+     * @param {Object} [options={}] The options object.
+     * @param {number} [options.length=30] The maximum string length.
+     * @param {string} [options.omission='...'] The string to indicate text is omitted.
+     * @param {RegExp|string} [options.separator] The separator pattern to truncate to.
+     * @returns {string} Returns the truncated string.
+     * @example
+     *
+     * _.truncate('hi-diddly-ho there, neighborino');
+     * // => 'hi-diddly-ho there, neighbo...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': ' '
+     * });
+     * // => 'hi-diddly-ho there,...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'length': 24,
+     *   'separator': /,? +/
+     * });
+     * // => 'hi-diddly-ho there...'
+     *
+     * _.truncate('hi-diddly-ho there, neighborino', {
+     *   'omission': ' [...]'
+     * });
+     * // => 'hi-diddly-ho there, neig [...]'
+     */
+    function truncate(string, options) {
+      var length = DEFAULT_TRUNC_LENGTH,
+          omission = DEFAULT_TRUNC_OMISSION;
+
+      if (isObject(options)) {
+        var separator = 'separator' in options ? options.separator : separator;
+        length = 'length' in options ? toInteger(options.length) : length;
+        omission = 'omission' in options ? baseToString(options.omission) : omission;
+      }
+      string = toString(string);
+
+      var strLength = string.length;
+      if (reHasComplexSymbol.test(string)) {
+        var strSymbols = stringToArray(string);
+        strLength = strSymbols.length;
+      }
+      if (length >= strLength) {
+        return string;
+      }
+      var end = length - stringSize(omission);
+      if (end < 1) {
+        return omission;
+      }
+      var result = strSymbols
+        ? castSlice(strSymbols, 0, end).join('')
+        : string.slice(0, end);
+
+      if (separator === undefined) {
+        return result + omission;
+      }
+      if (strSymbols) {
+        end += (result.length - end);
+      }
+      if (isRegExp(separator)) {
+        if (string.slice(end).search(separator)) {
+          var match,
+              substring = result;
+
+          if (!separator.global) {
+            separator = RegExp(separator.source, toString(reFlags.exec(separator)) + 'g');
+          }
+          separator.lastIndex = 0;
+          while ((match = separator.exec(substring))) {
+            var newEnd = match.index;
+          }
+          result = result.slice(0, newEnd === undefined ? end : newEnd);
+        }
+      } else if (string.indexOf(baseToString(separator), end) != end) {
+        var index = result.lastIndexOf(separator);
+        if (index > -1) {
+          result = result.slice(0, index);
+        }
+      }
+      return result + omission;
+    }
+
+    /**
+     * The inverse of `_.escape`; this method converts the HTML entities
+     * `&amp;`, `&lt;`, `&gt;`, `&quot;`, `&#39;`, and `&#96;` in `string` to
+     * their corresponding characters.
+     *
+     * **Note:** No other HTML entities are unescaped. To unescape additional
+     * HTML entities use a third-party library like [_he_](https://mths.be/he).
+     *
+     * @static
+     * @memberOf _
+     * @since 0.6.0
+     * @category String
+     * @param {string} [string=''] The string to unescape.
+     * @returns {string} Returns the unescaped string.
+     * @example
+     *
+     * _.unescape('fred, barney, &amp; pebbles');
+     * // => 'fred, barney, & pebbles'
+     */
+    function unescape(string) {
+      string = toString(string);
+      return (string && reHasEscapedHtml.test(string))
+        ? string.replace(reEscapedHtml, unescapeHtmlChar)
+        : string;
+    }
+
+    /**
+     * Converts `string`, as space separated words, to upper case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the upper cased string.
+     * @example
+     *
+     * _.upperCase('--foo-bar');
+     * // => 'FOO BAR'
+     *
+     * _.upperCase('fooBar');
+     * // => 'FOO BAR'
+     *
+     * _.upperCase('__foo_bar__');
+     * // => 'FOO BAR'
+     */
+    var upperCase = createCompounder(function(result, word, index) {
+      return result + (index ? ' ' : '') + word.toUpperCase();
+    });
+
+    /**
+     * Converts the first character of `string` to upper case.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category String
+     * @param {string} [string=''] The string to convert.
+     * @returns {string} Returns the converted string.
+     * @example
+     *
+     * _.upperFirst('fred');
+     * // => 'Fred'
+     *
+     * _.upperFirst('FRED');
+     * // => 'FRED'
+     */
+    var upperFirst = createCaseFirst('toUpperCase');
+
+    /**
+     * Splits `string` into an array of its words.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category String
+     * @param {string} [string=''] The string to inspect.
+     * @param {RegExp|string} [pattern] The pattern to match words.
+     * @param- {Object} [guard] Enables use as an iteratee for methods like `_.map`.
+     * @returns {Array} Returns the words of `string`.
+     * @example
+     *
+     * _.words('fred, barney, & pebbles');
+     * // => ['fred', 'barney', 'pebbles']
+     *
+     * _.words('fred, barney, & pebbles', /[^, ]+/g);
+     * // => ['fred', 'barney', '&', 'pebbles']
+     */
+    function words(string, pattern, guard) {
+      string = toString(string);
+      pattern = guard ? undefined : pattern;
+
+      if (pattern === undefined) {
+        pattern = reHasComplexWord.test(string) ? reComplexWord : reBasicWord;
+      }
+      return string.match(pattern) || [];
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Attempts to invoke `func`, returning either the result or the caught error
+     * object. Any additional arguments are provided to `func` when it's invoked.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Function} func The function to attempt.
+     * @param {...*} [args] The arguments to invoke `func` with.
+     * @returns {*} Returns the `func` result or error object.
+     * @example
+     *
+     * // Avoid throwing errors for invalid selectors.
+     * var elements = _.attempt(function(selector) {
+     *   return document.querySelectorAll(selector);
+     * }, '>_>');
+     *
+     * if (_.isError(elements)) {
+     *   elements = [];
+     * }
+     */
+    var attempt = rest(function(func, args) {
+      try {
+        return apply(func, undefined, args);
+      } catch (e) {
+        return isError(e) ? e : new Error(e);
+      }
+    });
+
+    /**
+     * Binds methods of an object to the object itself, overwriting the existing
+     * method.
+     *
+     * **Note:** This method doesn't set the "length" property of bound functions.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {Object} object The object to bind and assign the bound methods to.
+     * @param {...(string|string[])} methodNames The object method names to bind.
+     * @returns {Object} Returns `object`.
+     * @example
+     *
+     * var view = {
+     *   'label': 'docs',
+     *   'onClick': function() {
+     *     console.log('clicked ' + this.label);
+     *   }
+     * };
+     *
+     * _.bindAll(view, 'onClick');
+     * jQuery(element).on('click', view.onClick);
+     * // => Logs 'clicked docs' when clicked.
+     */
+    var bindAll = rest(function(object, methodNames) {
+      arrayEach(baseFlatten(methodNames, 1), function(key) {
+        key = toKey(key);
+        object[key] = bind(object[key], object);
+      });
+      return object;
+    });
+
+    /**
+     * Creates a function that iterates over `pairs` and invokes the corresponding
+     * function of the first predicate to return truthy. The predicate-function
+     * pairs are invoked with the `this` binding and arguments of the created
+     * function.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {Array} pairs The predicate-function pairs.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.cond([
+     *   [_.matches({ 'a': 1 }),           _.constant('matches A')],
+     *   [_.conforms({ 'b': _.isNumber }), _.constant('matches B')],
+     *   [_.constant(true),                _.constant('no match')]
+     * ]);
+     *
+     * func({ 'a': 1, 'b': 2 });
+     * // => 'matches A'
+     *
+     * func({ 'a': 0, 'b': 1 });
+     * // => 'matches B'
+     *
+     * func({ 'a': '1', 'b': '2' });
+     * // => 'no match'
+     */
+    function cond(pairs) {
+      var length = pairs ? pairs.length : 0,
+          toIteratee = getIteratee();
+
+      pairs = !length ? [] : arrayMap(pairs, function(pair) {
+        if (typeof pair[1] != 'function') {
+          throw new TypeError(FUNC_ERROR_TEXT);
+        }
+        return [toIteratee(pair[0]), pair[1]];
+      });
+
+      return rest(function(args) {
+        var index = -1;
+        while (++index < length) {
+          var pair = pairs[index];
+          if (apply(pair[0], this, args)) {
+            return apply(pair[1], this, args);
+          }
+        }
+      });
+    }
+
+    /**
+     * Creates a function that invokes the predicate properties of `source` with
+     * the corresponding property values of a given object, returning `true` if
+     * all predicates return truthy, else `false`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {Object} source The object of property predicates to conform to.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36 },
+     *   { 'user': 'fred',   'age': 40 }
+     * ];
+     *
+     * _.filter(users, _.conforms({ 'age': _.partial(_.gt, _, 38) }));
+     * // => [{ 'user': 'fred', 'age': 40 }]
+     */
+    function conforms(source) {
+      return baseConforms(baseClone(source, true));
+    }
+
+    /**
+     * Creates a function that returns `value`.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Util
+     * @param {*} value The value to return from the new function.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     * var getter = _.constant(object);
+     *
+     * getter() === object;
+     * // => true
+     */
+    function constant(value) {
+      return function() {
+        return value;
+      };
+    }
+
+    /**
+     * Creates a function that returns the result of invoking the given functions
+     * with the `this` binding of the created function, where each successive
+     * invocation is supplied the return value of the previous.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {...(Function|Function[])} [funcs] Functions to invoke.
+     * @returns {Function} Returns the new function.
+     * @see _.flowRight
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flow(_.add, square);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flow = createFlow();
+
+    /**
+     * This method is like `_.flow` except that it creates a function that
+     * invokes the given functions from right to left.
+     *
+     * @static
+     * @since 3.0.0
+     * @memberOf _
+     * @category Util
+     * @param {...(Function|Function[])} [funcs] Functions to invoke.
+     * @returns {Function} Returns the new function.
+     * @see _.flow
+     * @example
+     *
+     * function square(n) {
+     *   return n * n;
+     * }
+     *
+     * var addSquare = _.flowRight(square, _.add);
+     * addSquare(1, 2);
+     * // => 9
+     */
+    var flowRight = createFlow(true);
+
+    /**
+     * This method returns the first argument given to it.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {*} value Any value.
+     * @returns {*} Returns `value`.
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * _.identity(object) === object;
+     * // => true
+     */
+    function identity(value) {
+      return value;
+    }
+
+    /**
+     * Creates a function that invokes `func` with the arguments of the created
+     * function. If `func` is a property name, the created function returns the
+     * property value for a given element. If `func` is an array or object, the
+     * created function returns `true` for elements that contain the equivalent
+     * source properties, otherwise it returns `false`.
+     *
+     * @static
+     * @since 4.0.0
+     * @memberOf _
+     * @category Util
+     * @param {*} [func=_.identity] The value to convert to a callback.
+     * @returns {Function} Returns the callback.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * // The `_.matches` iteratee shorthand.
+     * _.filter(users, _.iteratee({ 'user': 'barney', 'active': true }));
+     * // => [{ 'user': 'barney', 'age': 36, 'active': true }]
+     *
+     * // The `_.matchesProperty` iteratee shorthand.
+     * _.filter(users, _.iteratee(['user', 'fred']));
+     * // => [{ 'user': 'fred', 'age': 40 }]
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.map(users, _.iteratee('user'));
+     * // => ['barney', 'fred']
+     *
+     * // Create custom iteratee shorthands.
+     * _.iteratee = _.wrap(_.iteratee, function(iteratee, func) {
+     *   return !_.isRegExp(func) ? iteratee(func) : function(string) {
+     *     return func.test(string);
+     *   };
+     * });
+     *
+     * _.filter(['abc', 'def'], /ef/);
+     * // => ['def']
+     */
+    function iteratee(func) {
+      return baseIteratee(typeof func == 'function' ? func : baseClone(func, true));
+    }
+
+    /**
+     * Creates a function that performs a partial deep comparison between a given
+     * object and `source`, returning `true` if the given object has equivalent
+     * property values, else `false`. The created function is equivalent to
+     * `_.isMatch` with a `source` partially applied.
+     *
+     * **Note:** This method supports comparing the same values as `_.isEqual`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Object} source The object of property values to match.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney', 'age': 36, 'active': true },
+     *   { 'user': 'fred',   'age': 40, 'active': false }
+     * ];
+     *
+     * _.filter(users, _.matches({ 'age': 40, 'active': false }));
+     * // => [{ 'user': 'fred', 'age': 40, 'active': false }]
+     */
+    function matches(source) {
+      return baseMatches(baseClone(source, true));
+    }
+
+    /**
+     * Creates a function that performs a partial deep comparison between the
+     * value at `path` of a given object to `srcValue`, returning `true` if the
+     * object value is equivalent, else `false`.
+     *
+     * **Note:** This method supports comparing the same values as `_.isEqual`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.2.0
+     * @category Util
+     * @param {Array|string} path The path of the property to get.
+     * @param {*} srcValue The value to match.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var users = [
+     *   { 'user': 'barney' },
+     *   { 'user': 'fred' }
+     * ];
+     *
+     * _.find(users, _.matchesProperty('user', 'fred'));
+     * // => { 'user': 'fred' }
+     */
+    function matchesProperty(path, srcValue) {
+      return baseMatchesProperty(path, baseClone(srcValue, true));
+    }
+
+    /**
+     * Creates a function that invokes the method at `path` of a given object.
+     * Any additional arguments are provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Util
+     * @param {Array|string} path The path of the method to invoke.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': _.constant(2) } },
+     *   { 'a': { 'b': _.constant(1) } }
+     * ];
+     *
+     * _.map(objects, _.method('a.b'));
+     * // => [2, 1]
+     *
+     * _.map(objects, _.method(['a', 'b']));
+     * // => [2, 1]
+     */
+    var method = rest(function(path, args) {
+      return function(object) {
+        return baseInvoke(object, path, args);
+      };
+    });
+
+    /**
+     * The opposite of `_.method`; this method creates a function that invokes
+     * the method at a given path of `object`. Any additional arguments are
+     * provided to the invoked method.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.7.0
+     * @category Util
+     * @param {Object} object The object to query.
+     * @param {...*} [args] The arguments to invoke the method with.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var array = _.times(3, _.constant),
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.methodOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.methodOf(object));
+     * // => [2, 0]
+     */
+    var methodOf = rest(function(object, args) {
+      return function(path) {
+        return baseInvoke(object, path, args);
+      };
+    });
+
+    /**
+     * Adds all own enumerable string keyed function properties of a source
+     * object to the destination object. If `object` is a function, then methods
+     * are added to its prototype as well.
+     *
+     * **Note:** Use `_.runInContext` to create a pristine `lodash` function to
+     * avoid conflicts caused by modifying the original.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {Function|Object} [object=lodash] The destination object.
+     * @param {Object} source The object of functions to add.
+     * @param {Object} [options={}] The options object.
+     * @param {boolean} [options.chain=true] Specify whether mixins are chainable.
+     * @returns {Function|Object} Returns `object`.
+     * @example
+     *
+     * function vowels(string) {
+     *   return _.filter(string, function(v) {
+     *     return /[aeiou]/i.test(v);
+     *   });
+     * }
+     *
+     * _.mixin({ 'vowels': vowels });
+     * _.vowels('fred');
+     * // => ['e']
+     *
+     * _('fred').vowels().value();
+     * // => ['e']
+     *
+     * _.mixin({ 'vowels': vowels }, { 'chain': false });
+     * _('fred').vowels();
+     * // => ['e']
+     */
+    function mixin(object, source, options) {
+      var props = keys(source),
+          methodNames = baseFunctions(source, props);
+
+      if (options == null &&
+          !(isObject(source) && (methodNames.length || !props.length))) {
+        options = source;
+        source = object;
+        object = this;
+        methodNames = baseFunctions(source, keys(source));
+      }
+      var chain = !(isObject(options) && 'chain' in options) || !!options.chain,
+          isFunc = isFunction(object);
+
+      arrayEach(methodNames, function(methodName) {
+        var func = source[methodName];
+        object[methodName] = func;
+        if (isFunc) {
+          object.prototype[methodName] = function() {
+            var chainAll = this.__chain__;
+            if (chain || chainAll) {
+              var result = object(this.__wrapped__),
+                  actions = result.__actions__ = copyArray(this.__actions__);
+
+              actions.push({ 'func': func, 'args': arguments, 'thisArg': object });
+              result.__chain__ = chainAll;
+              return result;
+            }
+            return func.apply(object, arrayPush([this.value()], arguments));
+          };
+        }
+      });
+
+      return object;
+    }
+
+    /**
+     * Reverts the `_` variable to its previous value and returns a reference to
+     * the `lodash` function.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @returns {Function} Returns the `lodash` function.
+     * @example
+     *
+     * var lodash = _.noConflict();
+     */
+    function noConflict() {
+      if (root._ === this) {
+        root._ = oldDash;
+      }
+      return this;
+    }
+
+    /**
+     * A no-operation function that returns `undefined` regardless of the
+     * arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.3.0
+     * @category Util
+     * @example
+     *
+     * var object = { 'user': 'fred' };
+     *
+     * _.noop(object) === undefined;
+     * // => true
+     */
+    function noop() {
+      // No operation performed.
+    }
+
+    /**
+     * Creates a function that returns its nth argument. If `n` is negative,
+     * the nth argument from the end is returned.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {number} [n=0] The index of the argument to return.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.nthArg(1);
+     * func('a', 'b', 'c', 'd');
+     * // => 'b'
+     *
+     * var func = _.nthArg(-2);
+     * func('a', 'b', 'c', 'd');
+     * // => 'c'
+     */
+    function nthArg(n) {
+      n = toInteger(n);
+      return rest(function(args) {
+        return baseNth(args, n);
+      });
+    }
+
+    /**
+     * Creates a function that invokes `iteratees` with the arguments it receives
+     * and returns their results.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [iteratees=[_.identity]] The iteratees to invoke.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.over(Math.max, Math.min);
+     *
+     * func(1, 2, 3, 4);
+     * // => [4, 1]
+     */
+    var over = createOver(arrayMap);
+
+    /**
+     * Creates a function that checks if **all** of the `predicates` return
+     * truthy when invoked with the arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [predicates=[_.identity]] The predicates to check.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.overEvery(Boolean, isFinite);
+     *
+     * func('1');
+     * // => true
+     *
+     * func(null);
+     * // => false
+     *
+     * func(NaN);
+     * // => false
+     */
+    var overEvery = createOver(arrayEvery);
+
+    /**
+     * Creates a function that checks if **any** of the `predicates` return
+     * truthy when invoked with the arguments it receives.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {...(Array|Array[]|Function|Function[]|Object|Object[]|string|string[])}
+     *  [predicates=[_.identity]] The predicates to check.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var func = _.overSome(Boolean, isFinite);
+     *
+     * func('1');
+     * // => true
+     *
+     * func(null);
+     * // => true
+     *
+     * func(NaN);
+     * // => false
+     */
+    var overSome = createOver(arraySome);
+
+    /**
+     * Creates a function that returns the value at `path` of a given object.
+     *
+     * @static
+     * @memberOf _
+     * @since 2.4.0
+     * @category Util
+     * @param {Array|string} path The path of the property to get.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var objects = [
+     *   { 'a': { 'b': 2 } },
+     *   { 'a': { 'b': 1 } }
+     * ];
+     *
+     * _.map(objects, _.property('a.b'));
+     * // => [2, 1]
+     *
+     * _.map(_.sortBy(objects, _.property(['a', 'b'])), 'a.b');
+     * // => [1, 2]
+     */
+    function property(path) {
+      return isKey(path) ? baseProperty(toKey(path)) : basePropertyDeep(path);
+    }
+
+    /**
+     * The opposite of `_.property`; this method creates a function that returns
+     * the value at a given path of `object`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.0.0
+     * @category Util
+     * @param {Object} object The object to query.
+     * @returns {Function} Returns the new function.
+     * @example
+     *
+     * var array = [0, 1, 2],
+     *     object = { 'a': array, 'b': array, 'c': array };
+     *
+     * _.map(['a[2]', 'c[0]'], _.propertyOf(object));
+     * // => [2, 0]
+     *
+     * _.map([['a', '2'], ['c', '0']], _.propertyOf(object));
+     * // => [2, 0]
+     */
+    function propertyOf(object) {
+      return function(path) {
+        return object == null ? undefined : baseGet(object, path);
+      };
+    }
+
+    /**
+     * Creates an array of numbers (positive and/or negative) progressing from
+     * `start` up to, but not including, `end`. A step of `-1` is used if a negative
+     * `start` is specified without an `end` or `step`. If `end` is not specified,
+     * it's set to `start` with `start` then set to `0`.
+     *
+     * **Note:** JavaScript follows the IEEE-754 standard for resolving
+     * floating-point values which can produce unexpected results.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @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 decrement by.
+     * @returns {Array} Returns the new array of numbers.
+     * @see _.inRange, _.rangeRight
+     * @example
+     *
+     * _.range(4);
+     * // => [0, 1, 2, 3]
+     *
+     * _.range(-4);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 5);
+     * // => [1, 2, 3, 4]
+     *
+     * _.range(0, 20, 5);
+     * // => [0, 5, 10, 15]
+     *
+     * _.range(0, -4, -1);
+     * // => [0, -1, -2, -3]
+     *
+     * _.range(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.range(0);
+     * // => []
+     */
+    var range = createRange();
+
+    /**
+     * This method is like `_.range` except that it populates values in
+     * descending order.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @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 decrement by.
+     * @returns {Array} Returns the new array of numbers.
+     * @see _.inRange, _.range
+     * @example
+     *
+     * _.rangeRight(4);
+     * // => [3, 2, 1, 0]
+     *
+     * _.rangeRight(-4);
+     * // => [-3, -2, -1, 0]
+     *
+     * _.rangeRight(1, 5);
+     * // => [4, 3, 2, 1]
+     *
+     * _.rangeRight(0, 20, 5);
+     * // => [15, 10, 5, 0]
+     *
+     * _.rangeRight(0, -4, -1);
+     * // => [-3, -2, -1, 0]
+     *
+     * _.rangeRight(1, 4, 0);
+     * // => [1, 1, 1]
+     *
+     * _.rangeRight(0);
+     * // => []
+     */
+    var rangeRight = createRange(true);
+
+    /**
+     * Invokes the iteratee `n` times, returning an array of the results of
+     * each invocation. The iteratee is invoked with one argument; (index).
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {number} n The number of times to invoke `iteratee`.
+     * @param {Function} [iteratee=_.identity] The function invoked per iteration.
+     * @returns {Array} Returns the array of results.
+     * @example
+     *
+     * _.times(3, String);
+     * // => ['0', '1', '2']
+     *
+     *  _.times(4, _.constant(true));
+     * // => [true, true, true, true]
+     */
+    function times(n, iteratee) {
+      n = toInteger(n);
+      if (n < 1 || n > MAX_SAFE_INTEGER) {
+        return [];
+      }
+      var index = MAX_ARRAY_LENGTH,
+          length = nativeMin(n, MAX_ARRAY_LENGTH);
+
+      iteratee = getIteratee(iteratee);
+      n -= MAX_ARRAY_LENGTH;
+
+      var result = baseTimes(length, iteratee);
+      while (++index < n) {
+        iteratee(index);
+      }
+      return result;
+    }
+
+    /**
+     * Converts `value` to a property path array.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Util
+     * @param {*} value The value to convert.
+     * @returns {Array} Returns the new property path array.
+     * @example
+     *
+     * _.toPath('a.b.c');
+     * // => ['a', 'b', 'c']
+     *
+     * _.toPath('a[0].b.c');
+     * // => ['a', '0', 'b', 'c']
+     *
+     * var path = ['a', 'b', 'c'],
+     *     newPath = _.toPath(path);
+     *
+     * console.log(newPath);
+     * // => ['a', 'b', 'c']
+     *
+     * console.log(path === newPath);
+     * // => false
+     */
+    function toPath(value) {
+      if (isArray(value)) {
+        return arrayMap(value, toKey);
+      }
+      return isSymbol(value) ? [value] : copyArray(stringToPath(value));
+    }
+
+    /**
+     * Generates a unique ID. If `prefix` is given, the ID is appended to it.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Util
+     * @param {string} [prefix=''] The value to prefix the ID with.
+     * @returns {string} Returns the unique ID.
+     * @example
+     *
+     * _.uniqueId('contact_');
+     * // => 'contact_104'
+     *
+     * _.uniqueId();
+     * // => '105'
+     */
+    function uniqueId(prefix) {
+      var id = ++idCounter;
+      return toString(prefix) + id;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Adds two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.4.0
+     * @category Math
+     * @param {number} augend The first number in an addition.
+     * @param {number} addend The second number in an addition.
+     * @returns {number} Returns the total.
+     * @example
+     *
+     * _.add(6, 4);
+     * // => 10
+     */
+    var add = createMathOperation(function(augend, addend) {
+      return augend + addend;
+    });
+
+    /**
+     * Computes `number` rounded up to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round up.
+     * @param {number} [precision=0] The precision to round up to.
+     * @returns {number} Returns the rounded up number.
+     * @example
+     *
+     * _.ceil(4.006);
+     * // => 5
+     *
+     * _.ceil(6.004, 2);
+     * // => 6.01
+     *
+     * _.ceil(6040, -2);
+     * // => 6100
+     */
+    var ceil = createRound('ceil');
+
+    /**
+     * Divide two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {number} dividend The first number in a division.
+     * @param {number} divisor The second number in a division.
+     * @returns {number} Returns the quotient.
+     * @example
+     *
+     * _.divide(6, 4);
+     * // => 1.5
+     */
+    var divide = createMathOperation(function(dividend, divisor) {
+      return dividend / divisor;
+    });
+
+    /**
+     * Computes `number` rounded down to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round down.
+     * @param {number} [precision=0] The precision to round down to.
+     * @returns {number} Returns the rounded down number.
+     * @example
+     *
+     * _.floor(4.006);
+     * // => 4
+     *
+     * _.floor(0.046, 2);
+     * // => 0.04
+     *
+     * _.floor(4060, -2);
+     * // => 4000
+     */
+    var floor = createRound('floor');
+
+    /**
+     * Computes the maximum value of `array`. If `array` is empty or falsey,
+     * `undefined` is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * _.max([4, 2, 8, 6]);
+     * // => 8
+     *
+     * _.max([]);
+     * // => undefined
+     */
+    function max(array) {
+      return (array && array.length)
+        ? baseExtremum(array, identity, baseGt)
+        : undefined;
+    }
+
+    /**
+     * This method is like `_.max` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * the value is ranked. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {*} Returns the maximum value.
+     * @example
+     *
+     * var objects = [{ 'n': 1 }, { 'n': 2 }];
+     *
+     * _.maxBy(objects, function(o) { return o.n; });
+     * // => { 'n': 2 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.maxBy(objects, 'n');
+     * // => { 'n': 2 }
+     */
+    function maxBy(array, iteratee) {
+      return (array && array.length)
+        ? baseExtremum(array, getIteratee(iteratee), baseGt)
+        : undefined;
+    }
+
+    /**
+     * Computes the mean of the values in `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {number} Returns the mean.
+     * @example
+     *
+     * _.mean([4, 2, 8, 6]);
+     * // => 5
+     */
+    function mean(array) {
+      return baseMean(array, identity);
+    }
+
+    /**
+     * This method is like `_.mean` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the value to be averaged.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the mean.
+     * @example
+     *
+     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+     *
+     * _.meanBy(objects, function(o) { return o.n; });
+     * // => 5
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.meanBy(objects, 'n');
+     * // => 5
+     */
+    function meanBy(array, iteratee) {
+      return baseMean(array, getIteratee(iteratee));
+    }
+
+    /**
+     * Computes the minimum value of `array`. If `array` is empty or falsey,
+     * `undefined` is returned.
+     *
+     * @static
+     * @since 0.1.0
+     * @memberOf _
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * _.min([4, 2, 8, 6]);
+     * // => 2
+     *
+     * _.min([]);
+     * // => undefined
+     */
+    function min(array) {
+      return (array && array.length)
+        ? baseExtremum(array, identity, baseLt)
+        : undefined;
+    }
+
+    /**
+     * This method is like `_.min` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the criterion by which
+     * the value is ranked. The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {*} Returns the minimum value.
+     * @example
+     *
+     * var objects = [{ 'n': 1 }, { 'n': 2 }];
+     *
+     * _.minBy(objects, function(o) { return o.n; });
+     * // => { 'n': 1 }
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.minBy(objects, 'n');
+     * // => { 'n': 1 }
+     */
+    function minBy(array, iteratee) {
+      return (array && array.length)
+        ? baseExtremum(array, getIteratee(iteratee), baseLt)
+        : undefined;
+    }
+
+    /**
+     * Multiply two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.7.0
+     * @category Math
+     * @param {number} multiplier The first number in a multiplication.
+     * @param {number} multiplicand The second number in a multiplication.
+     * @returns {number} Returns the product.
+     * @example
+     *
+     * _.multiply(6, 4);
+     * // => 24
+     */
+    var multiply = createMathOperation(function(multiplier, multiplicand) {
+      return multiplier * multiplicand;
+    });
+
+    /**
+     * Computes `number` rounded to `precision`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.10.0
+     * @category Math
+     * @param {number} number The number to round.
+     * @param {number} [precision=0] The precision to round to.
+     * @returns {number} Returns the rounded number.
+     * @example
+     *
+     * _.round(4.006);
+     * // => 4
+     *
+     * _.round(4.006, 2);
+     * // => 4.01
+     *
+     * _.round(4060, -2);
+     * // => 4100
+     */
+    var round = createRound('round');
+
+    /**
+     * Subtract two numbers.
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {number} minuend The first number in a subtraction.
+     * @param {number} subtrahend The second number in a subtraction.
+     * @returns {number} Returns the difference.
+     * @example
+     *
+     * _.subtract(6, 4);
+     * // => 2
+     */
+    var subtract = createMathOperation(function(minuend, subtrahend) {
+      return minuend - subtrahend;
+    });
+
+    /**
+     * Computes the sum of the values in `array`.
+     *
+     * @static
+     * @memberOf _
+     * @since 3.4.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * _.sum([4, 2, 8, 6]);
+     * // => 20
+     */
+    function sum(array) {
+      return (array && array.length)
+        ? baseSum(array, identity)
+        : 0;
+    }
+
+    /**
+     * This method is like `_.sum` except that it accepts `iteratee` which is
+     * invoked for each element in `array` to generate the value to be summed.
+     * The iteratee is invoked with one argument: (value).
+     *
+     * @static
+     * @memberOf _
+     * @since 4.0.0
+     * @category Math
+     * @param {Array} array The array to iterate over.
+     * @param {Array|Function|Object|string} [iteratee=_.identity]
+     *  The iteratee invoked per element.
+     * @returns {number} Returns the sum.
+     * @example
+     *
+     * var objects = [{ 'n': 4 }, { 'n': 2 }, { 'n': 8 }, { 'n': 6 }];
+     *
+     * _.sumBy(objects, function(o) { return o.n; });
+     * // => 20
+     *
+     * // The `_.property` iteratee shorthand.
+     * _.sumBy(objects, 'n');
+     * // => 20
+     */
+    function sumBy(array, iteratee) {
+      return (array && array.length)
+        ? baseSum(array, getIteratee(iteratee))
+        : 0;
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // Add methods that return wrapped values in chain sequences.
+    lodash.after = after;
+    lodash.ary = ary;
+    lodash.assign = assign;
+    lodash.assignIn = assignIn;
+    lodash.assignInWith = assignInWith;
+    lodash.assignWith = assignWith;
+    lodash.at = at;
+    lodash.before = before;
+    lodash.bind = bind;
+    lodash.bindAll = bindAll;
+    lodash.bindKey = bindKey;
+    lodash.castArray = castArray;
+    lodash.chain = chain;
+    lodash.chunk = chunk;
+    lodash.compact = compact;
+    lodash.concat = concat;
+    lodash.cond = cond;
+    lodash.conforms = conforms;
+    lodash.constant = constant;
+    lodash.countBy = countBy;
+    lodash.create = create;
+    lodash.curry = curry;
+    lodash.curryRight = curryRight;
+    lodash.debounce = debounce;
+    lodash.defaults = defaults;
+    lodash.defaultsDeep = defaultsDeep;
+    lodash.defer = defer;
+    lodash.delay = delay;
+    lodash.difference = difference;
+    lodash.differenceBy = differenceBy;
+    lodash.differenceWith = differenceWith;
+    lodash.drop = drop;
+    lodash.dropRight = dropRight;
+    lodash.dropRightWhile = dropRightWhile;
+    lodash.dropWhile = dropWhile;
+    lodash.fill = fill;
+    lodash.filter = filter;
+    lodash.flatMap = flatMap;
+    lodash.flatMapDeep = flatMapDeep;
+    lodash.flatMapDepth = flatMapDepth;
+    lodash.flatten = flatten;
+    lodash.flattenDeep = flattenDeep;
+    lodash.flattenDepth = flattenDepth;
+    lodash.flip = flip;
+    lodash.flow = flow;
+    lodash.flowRight = flowRight;
+    lodash.fromPairs = fromPairs;
+    lodash.functions = functions;
+    lodash.functionsIn = functionsIn;
+    lodash.groupBy = groupBy;
+    lodash.initial = initial;
+    lodash.intersection = intersection;
+    lodash.intersectionBy = intersectionBy;
+    lodash.intersectionWith = intersectionWith;
+    lodash.invert = invert;
+    lodash.invertBy = invertBy;
+    lodash.invokeMap = invokeMap;
+    lodash.iteratee = iteratee;
+    lodash.keyBy = keyBy;
+    lodash.keys = keys;
+    lodash.keysIn = keysIn;
+    lodash.map = map;
+    lodash.mapKeys = mapKeys;
+    lodash.mapValues = mapValues;
+    lodash.matches = matches;
+    lodash.matchesProperty = matchesProperty;
+    lodash.memoize = memoize;
+    lodash.merge = merge;
+    lodash.mergeWith = mergeWith;
+    lodash.method = method;
+    lodash.methodOf = methodOf;
+    lodash.mixin = mixin;
+    lodash.negate = negate;
+    lodash.nthArg = nthArg;
+    lodash.omit = omit;
+    lodash.omitBy = omitBy;
+    lodash.once = once;
+    lodash.orderBy = orderBy;
+    lodash.over = over;
+    lodash.overArgs = overArgs;
+    lodash.overEvery = overEvery;
+    lodash.overSome = overSome;
+    lodash.partial = partial;
+    lodash.partialRight = partialRight;
+    lodash.partition = partition;
+    lodash.pick = pick;
+    lodash.pickBy = pickBy;
+    lodash.property = property;
+    lodash.propertyOf = propertyOf;
+    lodash.pull = pull;
+    lodash.pullAll = pullAll;
+    lodash.pullAllBy = pullAllBy;
+    lodash.pullAllWith = pullAllWith;
+    lodash.pullAt = pullAt;
+    lodash.range = range;
+    lodash.rangeRight = rangeRight;
+    lodash.rearg = rearg;
+    lodash.reject = reject;
+    lodash.remove = remove;
+    lodash.rest = rest;
+    lodash.reverse = reverse;
+    lodash.sampleSize = sampleSize;
+    lodash.set = set;
+    lodash.setWith = setWith;
+    lodash.shuffle = shuffle;
+    lodash.slice = slice;
+    lodash.sortBy = sortBy;
+    lodash.sortedUniq = sortedUniq;
+    lodash.sortedUniqBy = sortedUniqBy;
+    lodash.split = split;
+    lodash.spread = spread;
+    lodash.tail = tail;
+    lodash.take = take;
+    lodash.takeRight = takeRight;
+    lodash.takeRightWhile = takeRightWhile;
+    lodash.takeWhile = takeWhile;
+    lodash.tap = tap;
+    lodash.throttle = throttle;
+    lodash.thru = thru;
+    lodash.toArray = toArray;
+    lodash.toPairs = toPairs;
+    lodash.toPairsIn = toPairsIn;
+    lodash.toPath = toPath;
+    lodash.toPlainObject = toPlainObject;
+    lodash.transform = transform;
+    lodash.unary = unary;
+    lodash.union = union;
+    lodash.unionBy = unionBy;
+    lodash.unionWith = unionWith;
+    lodash.uniq = uniq;
+    lodash.uniqBy = uniqBy;
+    lodash.uniqWith = uniqWith;
+    lodash.unset = unset;
+    lodash.unzip = unzip;
+    lodash.unzipWith = unzipWith;
+    lodash.update = update;
+    lodash.updateWith = updateWith;
+    lodash.values = values;
+    lodash.valuesIn = valuesIn;
+    lodash.without = without;
+    lodash.words = words;
+    lodash.wrap = wrap;
+    lodash.xor = xor;
+    lodash.xorBy = xorBy;
+    lodash.xorWith = xorWith;
+    lodash.zip = zip;
+    lodash.zipObject = zipObject;
+    lodash.zipObjectDeep = zipObjectDeep;
+    lodash.zipWith = zipWith;
+
+    // Add aliases.
+    lodash.entries = toPairs;
+    lodash.entriesIn = toPairsIn;
+    lodash.extend = assignIn;
+    lodash.extendWith = assignInWith;
+
+    // Add methods to `lodash.prototype`.
+    mixin(lodash, lodash);
+
+    /*------------------------------------------------------------------------*/
+
+    // Add methods that return unwrapped values in chain sequences.
+    lodash.add = add;
+    lodash.attempt = attempt;
+    lodash.camelCase = camelCase;
+    lodash.capitalize = capitalize;
+    lodash.ceil = ceil;
+    lodash.clamp = clamp;
+    lodash.clone = clone;
+    lodash.cloneDeep = cloneDeep;
+    lodash.cloneDeepWith = cloneDeepWith;
+    lodash.cloneWith = cloneWith;
+    lodash.deburr = deburr;
+    lodash.divide = divide;
+    lodash.endsWith = endsWith;
+    lodash.eq = eq;
+    lodash.escape = escape;
+    lodash.escapeRegExp = escapeRegExp;
+    lodash.every = every;
+    lodash.find = find;
+    lodash.findIndex = findIndex;
+    lodash.findKey = findKey;
+    lodash.findLast = findLast;
+    lodash.findLastIndex = findLastIndex;
+    lodash.findLastKey = findLastKey;
+    lodash.floor = floor;
+    lodash.forEach = forEach;
+    lodash.forEachRight = forEachRight;
+    lodash.forIn = forIn;
+    lodash.forInRight = forInRight;
+    lodash.forOwn = forOwn;
+    lodash.forOwnRight = forOwnRight;
+    lodash.get = get;
+    lodash.gt = gt;
+    lodash.gte = gte;
+    lodash.has = has;
+    lodash.hasIn = hasIn;
+    lodash.head = head;
+    lodash.identity = identity;
+    lodash.includes = includes;
+    lodash.indexOf = indexOf;
+    lodash.inRange = inRange;
+    lodash.invoke = invoke;
+    lodash.isArguments = isArguments;
+    lodash.isArray = isArray;
+    lodash.isArrayBuffer = isArrayBuffer;
+    lodash.isArrayLike = isArrayLike;
+    lodash.isArrayLikeObject = isArrayLikeObject;
+    lodash.isBoolean = isBoolean;
+    lodash.isBuffer = isBuffer;
+    lodash.isDate = isDate;
+    lodash.isElement = isElement;
+    lodash.isEmpty = isEmpty;
+    lodash.isEqual = isEqual;
+    lodash.isEqualWith = isEqualWith;
+    lodash.isError = isError;
+    lodash.isFinite = isFinite;
+    lodash.isFunction = isFunction;
+    lodash.isInteger = isInteger;
+    lodash.isLength = isLength;
+    lodash.isMap = isMap;
+    lodash.isMatch = isMatch;
+    lodash.isMatchWith = isMatchWith;
+    lodash.isNaN = isNaN;
+    lodash.isNative = isNative;
+    lodash.isNil = isNil;
+    lodash.isNull = isNull;
+    lodash.isNumber = isNumber;
+    lodash.isObject = isObject;
+    lodash.isObjectLike = isObjectLike;
+    lodash.isPlainObject = isPlainObject;
+    lodash.isRegExp = isRegExp;
+    lodash.isSafeInteger = isSafeInteger;
+    lodash.isSet = isSet;
+    lodash.isString = isString;
+    lodash.isSymbol = isSymbol;
+    lodash.isTypedArray = isTypedArray;
+    lodash.isUndefined = isUndefined;
+    lodash.isWeakMap = isWeakMap;
+    lodash.isWeakSet = isWeakSet;
+    lodash.join = join;
+    lodash.kebabCase = kebabCase;
+    lodash.last = last;
+    lodash.lastIndexOf = lastIndexOf;
+    lodash.lowerCase = lowerCase;
+    lodash.lowerFirst = lowerFirst;
+    lodash.lt = lt;
+    lodash.lte = lte;
+    lodash.max = max;
+    lodash.maxBy = maxBy;
+    lodash.mean = mean;
+    lodash.meanBy = meanBy;
+    lodash.min = min;
+    lodash.minBy = minBy;
+    lodash.multiply = multiply;
+    lodash.nth = nth;
+    lodash.noConflict = noConflict;
+    lodash.noop = noop;
+    lodash.now = now;
+    lodash.pad = pad;
+    lodash.padEnd = padEnd;
+    lodash.padStart = padStart;
+    lodash.parseInt = parseInt;
+    lodash.random = random;
+    lodash.reduce = reduce;
+    lodash.reduceRight = reduceRight;
+    lodash.repeat = repeat;
+    lodash.replace = replace;
+    lodash.result = result;
+    lodash.round = round;
+    lodash.runInContext = runInContext;
+    lodash.sample = sample;
+    lodash.size = size;
+    lodash.snakeCase = snakeCase;
+    lodash.some = some;
+    lodash.sortedIndex = sortedIndex;
+    lodash.sortedIndexBy = sortedIndexBy;
+    lodash.sortedIndexOf = sortedIndexOf;
+    lodash.sortedLastIndex = sortedLastIndex;
+    lodash.sortedLastIndexBy = sortedLastIndexBy;
+    lodash.sortedLastIndexOf = sortedLastIndexOf;
+    lodash.startCase = startCase;
+    lodash.startsWith = startsWith;
+    lodash.subtract = subtract;
+    lodash.sum = sum;
+    lodash.sumBy = sumBy;
+    lodash.template = template;
+    lodash.times = times;
+    lodash.toInteger = toInteger;
+    lodash.toLength = toLength;
+    lodash.toLower = toLower;
+    lodash.toNumber = toNumber;
+    lodash.toSafeInteger = toSafeInteger;
+    lodash.toString = toString;
+    lodash.toUpper = toUpper;
+    lodash.trim = trim;
+    lodash.trimEnd = trimEnd;
+    lodash.trimStart = trimStart;
+    lodash.truncate = truncate;
+    lodash.unescape = unescape;
+    lodash.uniqueId = uniqueId;
+    lodash.upperCase = upperCase;
+    lodash.upperFirst = upperFirst;
+
+    // Add aliases.
+    lodash.each = forEach;
+    lodash.eachRight = forEachRight;
+    lodash.first = head;
+
+    mixin(lodash, (function() {
+      var source = {};
+      baseForOwn(lodash, function(func, methodName) {
+        if (!hasOwnProperty.call(lodash.prototype, methodName)) {
+          source[methodName] = func;
+        }
+      });
+      return source;
+    }()), { 'chain': false });
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf _
+     * @type {string}
+     */
+    lodash.VERSION = VERSION;
+
+    // Assign default placeholders.
+    arrayEach(['bind', 'bindKey', 'curry', 'curryRight', 'partial', 'partialRight'], function(methodName) {
+      lodash[methodName].placeholder = lodash;
+    });
+
+    // Add `LazyWrapper` methods for `_.drop` and `_.take` variants.
+    arrayEach(['drop', 'take'], function(methodName, index) {
+      LazyWrapper.prototype[methodName] = function(n) {
+        var filtered = this.__filtered__;
+        if (filtered && !index) {
+          return new LazyWrapper(this);
+        }
+        n = n === undefined ? 1 : nativeMax(toInteger(n), 0);
+
+        var result = this.clone();
+        if (filtered) {
+          result.__takeCount__ = nativeMin(n, result.__takeCount__);
+        } else {
+          result.__views__.push({
+            'size': nativeMin(n, MAX_ARRAY_LENGTH),
+            'type': methodName + (result.__dir__ < 0 ? 'Right' : '')
+          });
+        }
+        return result;
+      };
+
+      LazyWrapper.prototype[methodName + 'Right'] = function(n) {
+        return this.reverse()[methodName](n).reverse();
+      };
+    });
+
+    // Add `LazyWrapper` methods that accept an `iteratee` value.
+    arrayEach(['filter', 'map', 'takeWhile'], function(methodName, index) {
+      var type = index + 1,
+          isFilter = type == LAZY_FILTER_FLAG || type == LAZY_WHILE_FLAG;
+
+      LazyWrapper.prototype[methodName] = function(iteratee) {
+        var result = this.clone();
+        result.__iteratees__.push({
+          'iteratee': getIteratee(iteratee, 3),
+          'type': type
+        });
+        result.__filtered__ = result.__filtered__ || isFilter;
+        return result;
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.head` and `_.last`.
+    arrayEach(['head', 'last'], function(methodName, index) {
+      var takeName = 'take' + (index ? 'Right' : '');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this[takeName](1).value()[0];
+      };
+    });
+
+    // Add `LazyWrapper` methods for `_.initial` and `_.tail`.
+    arrayEach(['initial', 'tail'], function(methodName, index) {
+      var dropName = 'drop' + (index ? '' : 'Right');
+
+      LazyWrapper.prototype[methodName] = function() {
+        return this.__filtered__ ? new LazyWrapper(this) : this[dropName](1);
+      };
+    });
+
+    LazyWrapper.prototype.compact = function() {
+      return this.filter(identity);
+    };
+
+    LazyWrapper.prototype.find = function(predicate) {
+      return this.filter(predicate).head();
+    };
+
+    LazyWrapper.prototype.findLast = function(predicate) {
+      return this.reverse().find(predicate);
+    };
+
+    LazyWrapper.prototype.invokeMap = rest(function(path, args) {
+      if (typeof path == 'function') {
+        return new LazyWrapper(this);
+      }
+      return this.map(function(value) {
+        return baseInvoke(value, path, args);
+      });
+    });
+
+    LazyWrapper.prototype.reject = function(predicate) {
+      predicate = getIteratee(predicate, 3);
+      return this.filter(function(value) {
+        return !predicate(value);
+      });
+    };
+
+    LazyWrapper.prototype.slice = function(start, end) {
+      start = toInteger(start);
+
+      var result = this;
+      if (result.__filtered__ && (start > 0 || end < 0)) {
+        return new LazyWrapper(result);
+      }
+      if (start < 0) {
+        result = result.takeRight(-start);
+      } else if (start) {
+        result = result.drop(start);
+      }
+      if (end !== undefined) {
+        end = toInteger(end);
+        result = end < 0 ? result.dropRight(-end) : result.take(end - start);
+      }
+      return result;
+    };
+
+    LazyWrapper.prototype.takeRightWhile = function(predicate) {
+      return this.reverse().takeWhile(predicate).reverse();
+    };
+
+    LazyWrapper.prototype.toArray = function() {
+      return this.take(MAX_ARRAY_LENGTH);
+    };
+
+    // Add `LazyWrapper` methods to `lodash.prototype`.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var checkIteratee = /^(?:filter|find|map|reject)|While$/.test(methodName),
+          isTaker = /^(?:head|last)$/.test(methodName),
+          lodashFunc = lodash[isTaker ? ('take' + (methodName == 'last' ? 'Right' : '')) : methodName],
+          retUnwrapped = isTaker || /^find/.test(methodName);
+
+      if (!lodashFunc) {
+        return;
+      }
+      lodash.prototype[methodName] = function() {
+        var value = this.__wrapped__,
+            args = isTaker ? [1] : arguments,
+            isLazy = value instanceof LazyWrapper,
+            iteratee = args[0],
+            useLazy = isLazy || isArray(value);
+
+        var interceptor = function(value) {
+          var result = lodashFunc.apply(lodash, arrayPush([value], args));
+          return (isTaker && chainAll) ? result[0] : result;
+        };
+
+        if (useLazy && checkIteratee && typeof iteratee == 'function' && iteratee.length != 1) {
+          // Avoid lazy use if the iteratee has a "length" value other than `1`.
+          isLazy = useLazy = false;
+        }
+        var chainAll = this.__chain__,
+            isHybrid = !!this.__actions__.length,
+            isUnwrapped = retUnwrapped && !chainAll,
+            onlyLazy = isLazy && !isHybrid;
+
+        if (!retUnwrapped && useLazy) {
+          value = onlyLazy ? value : new LazyWrapper(this);
+          var result = func.apply(value, args);
+          result.__actions__.push({ 'func': thru, 'args': [interceptor], 'thisArg': undefined });
+          return new LodashWrapper(result, chainAll);
+        }
+        if (isUnwrapped && onlyLazy) {
+          return func.apply(this, args);
+        }
+        result = this.thru(interceptor);
+        return isUnwrapped ? (isTaker ? result.value()[0] : result.value()) : result;
+      };
+    });
+
+    // Add `Array` methods to `lodash.prototype`.
+    arrayEach(['pop', 'push', 'shift', 'sort', 'splice', 'unshift'], function(methodName) {
+      var func = arrayProto[methodName],
+          chainName = /^(?:push|sort|unshift)$/.test(methodName) ? 'tap' : 'thru',
+          retUnwrapped = /^(?:pop|shift)$/.test(methodName);
+
+      lodash.prototype[methodName] = function() {
+        var args = arguments;
+        if (retUnwrapped && !this.__chain__) {
+          var value = this.value();
+          return func.apply(isArray(value) ? value : [], args);
+        }
+        return this[chainName](function(value) {
+          return func.apply(isArray(value) ? value : [], args);
+        });
+      };
+    });
+
+    // Map minified method names to their real names.
+    baseForOwn(LazyWrapper.prototype, function(func, methodName) {
+      var lodashFunc = lodash[methodName];
+      if (lodashFunc) {
+        var key = (lodashFunc.name + ''),
+            names = realNames[key] || (realNames[key] = []);
+
+        names.push({ 'name': methodName, 'func': lodashFunc });
+      }
+    });
+
+    realNames[createHybridWrapper(undefined, BIND_KEY_FLAG).name] = [{
+      'name': 'wrapper',
+      'func': undefined
+    }];
+
+    // Add methods to `LazyWrapper`.
+    LazyWrapper.prototype.clone = lazyClone;
+    LazyWrapper.prototype.reverse = lazyReverse;
+    LazyWrapper.prototype.value = lazyValue;
+
+    // Add chain sequence methods to the `lodash` wrapper.
+    lodash.prototype.at = wrapperAt;
+    lodash.prototype.chain = wrapperChain;
+    lodash.prototype.commit = wrapperCommit;
+    lodash.prototype.next = wrapperNext;
+    lodash.prototype.plant = wrapperPlant;
+    lodash.prototype.reverse = wrapperReverse;
+    lodash.prototype.toJSON = lodash.prototype.valueOf = lodash.prototype.value = wrapperValue;
+
+    if (iteratorSymbol) {
+      lodash.prototype[iteratorSymbol] = wrapperToIterator;
+    }
+    return lodash;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Export lodash.
+  var _ = runInContext();
+
+  // Expose Lodash on the free variable `window` or `self` when available so it's
+  // globally accessible, even when bundled with Browserify, Webpack, etc. This
+  // also prevents errors in cases where Lodash is loaded by a script tag in the
+  // presence of an AMD loader. See http://requirejs.org/docs/errors.html#mismatch
+  // for more details. Use `_.noConflict` to remove Lodash from the global object.
+  (freeWindow || freeSelf || {})._ = _;
+
+  // Some AMD build optimizers like r.js check for condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // Define as an anonymous module so, through path mapping, it can be
+    // referenced as the "underscore" module.
+    define(function() {
+      return _;
+    });
+  }
+  // Check for `exports` after `define` in case a build optimizer adds an `exports` object.
+  else if (freeExports && freeModule) {
+    // Export for Node.js.
+    if (moduleExports) {
+      (freeModule.exports = _)._ = _;
+    }
+    // Export for CommonJS support.
+    freeExports._ = _;
+  }
+  else {
+    // Export to the global object.
+    root._ = _;
+  }
+}.call(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/package.json b/views/ngXosViews/serviceGrid/src/vendor/lodash/package.json
new file mode 100644
index 0000000..914499d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/package.json
@@ -0,0 +1,52 @@
+{
+  "name": "lodash",
+  "version": "4.11.2",
+  "license": "MIT",
+  "private": true,
+  "main": "lodash.js",
+  "scripts": {
+    "build": "npm run build:main && npm run build:fp",
+    "build:fp": "node lib/fp/build-dist.js",
+    "build:fp-modules": "node lib/fp/build-modules.js",
+    "build:main": "node lib/main/build-dist.js",
+    "build:main-modules": "node lib/main/build-modules.js",
+    "doc": "node lib/main/build-doc github",
+    "doc:fp": "node lib/fp/build-doc",
+    "doc:site": "node lib/main/build-doc site",
+    "pretest": "npm run build",
+    "style": "npm run style:main && npm run style:fp && npm run style:perf && npm run style:test",
+    "style:fp": "jscs fp/*.js lib/**/*.js",
+    "style:main": "jscs lodash.js",
+    "style:perf": "jscs perf/*.js perf/**/*.js",
+    "style:test": "jscs test/*.js test/**/*.js",
+    "test": "npm run test:main && npm run test:fp",
+    "test:fp": "node test/test-fp",
+    "test:main": "node test/test",
+    "validate": "npm run style && npm run test"
+  },
+  "devDependencies": {
+    "async": "^1.5.2",
+    "benchmark": "^2.1.0",
+    "chalk": "^1.1.3",
+    "codecov.io": "~0.1.6",
+    "coveralls": "^2.11.9",
+    "curl-amd": "~0.8.12",
+    "docdown": "~0.5.1",
+    "dojo": "^1.11.1",
+    "ecstatic": "^1.4.0",
+    "fs-extra": "~0.28.0",
+    "glob": "^7.0.3",
+    "istanbul": "0.4.3",
+    "jquery": "^2.2.3",
+    "jscs": "^3.0.1",
+    "lodash": "4.10.0",
+    "platform": "^1.3.1",
+    "qunit-extras": "^1.5.0",
+    "qunitjs": "~1.23.1",
+    "request": "^2.69.0",
+    "requirejs": "^2.2.0",
+    "sauce-tunnel": "^2.4.0",
+    "uglify-js": "2.6.2",
+    "webpack": "^1.12.15"
+  }
+}
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/asset/perf-ui.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/asset/perf-ui.js
new file mode 100644
index 0000000..e3ed64b
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/asset/perf-ui.js
@@ -0,0 +1,131 @@
+;(function(window) {
+  'use strict';
+
+  /** The base path of the lodash builds. */
+  var basePath = '../';
+
+  /** The lodash build to load. */
+  var build = (build = /build=([^&]+)/.exec(location.search)) && decodeURIComponent(build[1]);
+
+  /** The other library to load. */
+  var other = (other = /other=([^&]+)/.exec(location.search)) && decodeURIComponent(other[1]);
+
+  /** The `ui` object. */
+  var ui = {};
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Registers an event listener on an element.
+   *
+   * @private
+   * @param {Element} element The element.
+   * @param {string} eventName The name of the event.
+   * @param {Function} handler The event handler.
+   * @returns {Element} The element.
+   */
+  function addListener(element, eventName, handler) {
+    if (typeof element.addEventListener != 'undefined') {
+      element.addEventListener(eventName, handler, false);
+    } else if (typeof element.attachEvent != 'undefined') {
+      element.attachEvent('on' + eventName, handler);
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Initialize controls.
+  addListener(window, 'load', function() {
+    function eventHandler(event) {
+      var buildIndex = buildList.selectedIndex,
+          otherIndex = otherList.selectedIndex,
+          search = location.search.replace(/^\?|&?(?:build|other)=[^&]*&?/g, '');
+
+      if (event.stopPropagation) {
+        event.stopPropagation();
+      } else {
+        event.cancelBubble = true;
+      }
+      location.href =
+        location.href.split('?')[0] + '?' +
+        (search ? search + '&' : '') +
+        'build=' + (buildIndex < 0 ? build : buildList[buildIndex].value) + '&' +
+        'other=' + (otherIndex < 0 ? other : otherList[otherIndex].value);
+    }
+
+    var span1 = document.createElement('span');
+    span1.style.cssText = 'float:right';
+    span1.innerHTML =
+      '<label for="perf-build">Build: </label>' +
+      '<select id="perf-build">' +
+      '<option value="lodash">lodash</option>' +
+      '</select>';
+
+    var span2 = document.createElement('span');
+    span2.style.cssText = 'float:right';
+    span2.innerHTML =
+      '<label for="perf-other">Other Library: </label>' +
+      '<select id="perf-other">' +
+      '<option value="underscore-dev">Underscore (development)</option>' +
+      '<option value="underscore">Underscore (production)</option>' +
+      '<option value="lodash">lodash</option>' +
+      '</select>';
+
+    var buildList = span1.lastChild,
+        otherList = span2.lastChild,
+        toolbar = document.getElementById('perf-toolbar');
+
+    toolbar.appendChild(span2);
+    toolbar.appendChild(span1);
+
+    buildList.selectedIndex = (function() {
+      switch (build) {
+        case 'lodash':
+        case null:                return 0;
+      }
+      return -1;
+    }());
+
+    otherList.selectedIndex = (function() {
+      switch (other) {
+        case 'underscore-dev':    return 0;
+        case 'lodash':            return 2;
+        case 'underscore':
+        case null:                return 1;
+      }
+      return -1;
+    }());
+
+    addListener(buildList, 'change', eventHandler);
+    addListener(otherList, 'change', eventHandler);
+  });
+
+  // The lodash build file path.
+  ui.buildPath = (function() {
+    var result;
+    switch (build) {
+      case null:                build  = 'lodash';
+      case 'lodash':            result = 'dist/lodash.min.js'; break;
+      default:                  return build;
+    }
+    return basePath + result;
+  }());
+
+  // The other library file path.
+  ui.otherPath = (function() {
+    var result;
+    switch (other) {
+      case 'lodash':            result = 'dist/lodash.min.js'; break;
+      case 'underscore-dev':    result = 'vendor/underscore/underscore.js'; break;
+      case null:                other  = 'underscore';
+      case 'underscore':        result = 'vendor/underscore/underscore-min.js'; break;
+      default:                  return other;
+    }
+    return basePath + result;
+  }());
+
+  ui.urlParams = { 'build': build, 'other': other };
+
+  window.ui = ui;
+
+}(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/index.html b/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/index.html
new file mode 100644
index 0000000..16d41bf
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/index.html
@@ -0,0 +1,69 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>lodash Performance Suite</title>
+    <style>
+      html, body {
+        margin: 0;
+        padding: 0;
+        height: 100%;
+      }
+      #FirebugUI {
+        top: 2em;
+      }
+      #perf-toolbar {
+        background-color: #EEE;
+        color: #5E740B;
+        font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+        font-size: small;
+        padding: 0.5em 0 0.5em 2em;
+        overflow: hidden;
+      }
+    </style>
+  </head>
+  <body>
+    <div id="perf-toolbar"></div>
+    <script src="../lodash.js"></script>
+    <script src="../node_modules/platform/platform.js"></script>
+    <script src="../node_modules/benchmark/benchmark.js"></script>
+    <script src="../vendor/firebug-lite/src/firebug-lite-debug.js"></script>
+    <script src="./asset/perf-ui.js"></script>
+    <script>
+      document.write('<script src="' + ui.buildPath + '"><\/script>');
+    </script>
+    <script>
+      var lodash = _.noConflict();
+    </script>
+    <script>
+      document.write('<script src="' + ui.otherPath + '"><\/script>');
+    </script>
+    <script src="perf.js"></script>
+    <script>
+      (function() {
+        var measured,
+            perfNow,
+            begin = new Date;
+
+        function init() {
+          var fbUI = document.getElementById('FirebugUI'),
+              fbDoc = fbUI && (fbDoc = fbUI.contentWindow || fbUI.contentDocument).document || fbDoc,
+              fbCommandLine = fbDoc && fbDoc.getElementById('fbCommandLine');
+
+          if (!fbCommandLine) {
+            return setTimeout(init, 15);
+          }
+          fbUI.style.height = (
+            Math.max(document.documentElement.clientHeight, document.body.clientHeight) -
+            document.getElementById('perf-toolbar').clientHeight
+          ) + 'px';
+
+          fbDoc.body.style.height = fbDoc.documentElement.style.height = '100%';
+          setTimeout(run, 15);
+        }
+
+        window.onload = init;
+      }());
+    </script>
+  </body>
+</html>
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/perf.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/perf.js
new file mode 100644
index 0000000..baee142
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/perf/perf.js
@@ -0,0 +1,1977 @@
+;(function() {
+
+  /** Used to access the Firebug Lite panel (set by `run`). */
+  var fbPanel;
+
+  /** Used as a safe reference for `undefined` in pre ES5 environments. */
+  var undefined;
+
+  /** Used as a reference to the global object. */
+  var root = typeof global == 'object' && global || this;
+
+  /** Method and object shortcuts. */
+  var phantom = root.phantom,
+      amd = root.define && define.amd,
+      argv = root.process && process.argv,
+      document = !phantom && root.document,
+      noop = function() {},
+      params = root.arguments,
+      system = root.system;
+
+  /** Add `console.log()` support for Rhino and RingoJS. */
+  var console = root.console || (root.console = { 'log': root.print });
+
+  /** The file path of the lodash file to test. */
+  var filePath = (function() {
+    var min = 0,
+        result = [];
+
+    if (phantom) {
+      result = params = phantom.args;
+    } else if (system) {
+      min = 1;
+      result = params = system.args;
+    } else if (argv) {
+      min = 2;
+      result = params = argv;
+    } else if (params) {
+      result = params;
+    }
+    var last = result[result.length - 1];
+    result = (result.length > min && !/perf(?:\.js)?$/.test(last)) ? last : '../lodash.js';
+
+    if (!amd) {
+      try {
+        result = require('fs').realpathSync(result);
+      } catch (e) {}
+
+      try {
+        result = require.resolve(result);
+      } catch (e) {}
+    }
+    return result;
+  }());
+
+  /** Used to match path separators. */
+  var rePathSeparator = /[\/\\]/;
+
+  /** Used to detect primitive types. */
+  var rePrimitive = /^(?:boolean|number|string|undefined)$/;
+
+  /** Used to match RegExp special characters. */
+  var reSpecialChars = /[.*+?^=!:${}()|[\]\/\\]/g;
+
+  /** The `ui` object. */
+  var ui = root.ui || (root.ui = {
+    'buildPath': basename(filePath, '.js'),
+    'otherPath': 'underscore'
+  });
+
+  /** The lodash build basename. */
+  var buildName = root.buildName = basename(ui.buildPath, '.js');
+
+  /** The other library basename. */
+  var otherName = root.otherName = (function() {
+    var result = basename(ui.otherPath, '.js');
+    return result + (result == buildName ? ' (2)' : '');
+  }());
+
+  /** Used to score performance. */
+  var score = { 'a': [], 'b': [] };
+
+  /** Used to queue benchmark suites. */
+  var suites = [];
+
+  /** Use a single "load" function. */
+  var load = (typeof require == 'function' && !amd)
+    ? require
+    : noop;
+
+  /** Load lodash. */
+  var lodash = root.lodash || (root.lodash = (
+    lodash = load(filePath) || root._,
+    lodash = lodash._ || lodash,
+    (lodash.runInContext ? lodash.runInContext(root) : lodash),
+    lodash.noConflict()
+  ));
+
+  /** Load Underscore. */
+  var _ = root.underscore || (root.underscore = (
+    _ = load('../vendor/underscore/underscore.js') || root._,
+    _._ || _
+  ));
+
+  /** Load Benchmark.js. */
+  var Benchmark = root.Benchmark || (root.Benchmark = (
+    Benchmark = load('../node_modules/benchmark/benchmark.js') || root.Benchmark,
+    Benchmark = Benchmark.Benchmark || Benchmark,
+    Benchmark.runInContext(lodash.extend({}, root, { '_': lodash }))
+  ));
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Gets the basename of the given `filePath`. If the file `extension` is passed,
+   * it will be removed from the basename.
+   *
+   * @private
+   * @param {string} path The file path to inspect.
+   * @param {string} extension The extension to remove.
+   * @returns {string} Returns the basename.
+   */
+  function basename(filePath, extension) {
+    var result = (filePath || '').split(rePathSeparator).pop();
+    return (arguments.length < 2)
+      ? result
+      : result.replace(RegExp(extension.replace(reSpecialChars, '\\$&') + '$'), '');
+  }
+
+  /**
+   * Computes the geometric mean (log-average) of an array of values.
+   * See http://en.wikipedia.org/wiki/Geometric_mean#Relationship_with_arithmetic_mean_of_logarithms.
+   *
+   * @private
+   * @param {Array} array The array of values.
+   * @returns {number} The geometric mean.
+   */
+  function getGeometricMean(array) {
+    return Math.pow(Math.E, lodash.reduce(array, function(sum, x) {
+      return sum + Math.log(x);
+    }, 0) / array.length) || 0;
+  }
+
+  /**
+   * Gets the Hz, i.e. operations per second, of `bench` adjusted for the
+   * margin of error.
+   *
+   * @private
+   * @param {Object} bench The benchmark object.
+   * @returns {number} Returns the adjusted Hz.
+   */
+  function getHz(bench) {
+    var result = 1 / (bench.stats.mean + bench.stats.moe);
+    return isFinite(result) ? result : 0;
+  }
+
+  /**
+   * Host objects can return type values that are different from their actual
+   * data type. The objects we are concerned with usually return non-primitive
+   * types of "object", "function", or "unknown".
+   *
+   * @private
+   * @param {*} object The owner of the property.
+   * @param {string} property The property to check.
+   * @returns {boolean} Returns `true` if the property value is a non-primitive, else `false`.
+   */
+  function isHostType(object, property) {
+    if (object == null) {
+      return false;
+    }
+    var type = typeof object[property];
+    return !rePrimitive.test(type) && (type != 'object' || !!object[property]);
+  }
+
+  /**
+   * Logs text to the console.
+   *
+   * @private
+   * @param {string} text The text to log.
+   */
+  function log(text) {
+    console.log(text + '');
+    if (fbPanel) {
+      // Scroll the Firebug Lite panel down.
+      fbPanel.scrollTop = fbPanel.scrollHeight;
+    }
+  }
+
+  /**
+   * Runs all benchmark suites.
+   *
+   * @private (@public in the browser)
+   */
+  function run() {
+    fbPanel = (fbPanel = root.document && document.getElementById('FirebugUI')) &&
+      (fbPanel = (fbPanel = fbPanel.contentWindow || fbPanel.contentDocument).document || fbPanel) &&
+      fbPanel.getElementById('fbPanel1');
+
+    log('\nSit back and relax, this may take a while.');
+    suites[0].run({ 'async': true });
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  lodash.extend(Benchmark.Suite.options, {
+    'onStart': function() {
+      log('\n' + this.name + ':');
+    },
+    'onCycle': function(event) {
+      log(event.target);
+    },
+    'onComplete': function() {
+      for (var index = 0, length = this.length; index < length; index++) {
+        var bench = this[index];
+        if (bench.error) {
+          var errored = true;
+        }
+      }
+      if (errored) {
+        log('There was a problem, skipping...');
+      }
+      else {
+        var formatNumber = Benchmark.formatNumber,
+            fastest = this.filter('fastest'),
+            fastestHz = getHz(fastest[0]),
+            slowest = this.filter('slowest'),
+            slowestHz = getHz(slowest[0]),
+            aHz = getHz(this[0]),
+            bHz = getHz(this[1]);
+
+        if (fastest.length > 1) {
+          log('It\'s too close to call.');
+          aHz = bHz = slowestHz;
+        }
+        else {
+          var percent = ((fastestHz / slowestHz) - 1) * 100;
+
+          log(
+            fastest[0].name + ' is ' +
+            formatNumber(percent < 1 ? percent.toFixed(2) : Math.round(percent)) +
+            '% faster.'
+          );
+        }
+        // Add score adjusted for margin of error.
+        score.a.push(aHz);
+        score.b.push(bHz);
+      }
+      // Remove current suite from queue.
+      suites.shift();
+
+      if (suites.length) {
+        // Run next suite.
+        suites[0].run({ 'async': true });
+      }
+      else {
+        var aMeanHz = getGeometricMean(score.a),
+            bMeanHz = getGeometricMean(score.b),
+            fastestMeanHz = Math.max(aMeanHz, bMeanHz),
+            slowestMeanHz = Math.min(aMeanHz, bMeanHz),
+            xFaster = fastestMeanHz / slowestMeanHz,
+            percentFaster = formatNumber(Math.round((xFaster - 1) * 100)),
+            message = 'is ' + percentFaster + '% ' + (xFaster == 1 ? '' : '(' + formatNumber(xFaster.toFixed(2)) + 'x) ') + 'faster than';
+
+        // Report results.
+        if (aMeanHz >= bMeanHz) {
+          log('\n' + buildName + ' ' + message + ' ' + otherName + '.');
+        } else {
+          log('\n' + otherName + ' ' + message + ' ' + buildName + '.');
+        }
+      }
+    }
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  lodash.extend(Benchmark.options, {
+    'async': true,
+    'setup': '\
+      var _ = global.underscore,\
+          lodash = global.lodash,\
+          belt = this.name == buildName ? lodash : _;\
+      \
+      var date = new Date,\
+          limit = 50,\
+          regexp = /x/,\
+          object = {},\
+          objects = Array(limit),\
+          numbers = Array(limit),\
+          fourNumbers = [5, 25, 10, 30],\
+          nestedNumbers = [1, [2], [3, [[4]]]],\
+          nestedObjects = [{}, [{}], [{}, [[{}]]]],\
+          twoNumbers = [12, 23];\
+      \
+      for (var index = 0; index < limit; index++) {\
+        numbers[index] = index;\
+        object["key" + index] = index;\
+        objects[index] = { "num": index };\
+      }\
+      var strNumbers = numbers + "";\
+      \
+      if (typeof assign != "undefined") {\
+        var _assign = _.assign || _.extend,\
+            lodashAssign = lodash.assign;\
+      }\
+      if (typeof bind != "undefined") {\
+        var thisArg = { "name": "fred" };\
+        \
+        var func = function(greeting, punctuation) {\
+          return (greeting || "hi") + " " + this.name + (punctuation || ".");\
+        };\
+        \
+        var _boundNormal = _.bind(func, thisArg),\
+            _boundMultiple = _boundNormal,\
+            _boundPartial = _.bind(func, thisArg, "hi");\
+        \
+        var lodashBoundNormal = lodash.bind(func, thisArg),\
+            lodashBoundMultiple = lodashBoundNormal,\
+            lodashBoundPartial = lodash.bind(func, thisArg, "hi");\
+        \
+        for (index = 0; index < 10; index++) {\
+          _boundMultiple = _.bind(_boundMultiple, { "name": "fred" + index });\
+          lodashBoundMultiple = lodash.bind(lodashBoundMultiple, { "name": "fred" + index });\
+        }\
+      }\
+      if (typeof bindAll != "undefined") {\
+        var bindAllCount = -1,\
+            bindAllObjects = Array(this.count);\
+        \
+        var funcNames = belt.reject(belt.functions(belt).slice(0, 40), function(funcName) {\
+          return /^_/.test(funcName);\
+        });\
+        \
+        // Potentially expensive.\n\
+        for (index = 0; index < this.count; index++) {\
+          bindAllObjects[index] = belt.reduce(funcNames, function(object, funcName) {\
+            object[funcName] = belt[funcName];\
+            return object;\
+          }, {});\
+        }\
+      }\
+      if (typeof chaining != "undefined") {\
+        var even = function(v) { return v % 2 == 0; },\
+            square = function(v) { return v * v; };\
+        \
+        var largeArray = belt.range(10000),\
+            _chaining = _(largeArray).chain(),\
+            lodashChaining = lodash(largeArray).chain();\
+      }\
+      if (typeof compact != "undefined") {\
+        var uncompacted = numbers.slice();\
+        uncompacted[2] = false;\
+        uncompacted[6] = null;\
+        uncompacted[18] = "";\
+      }\
+      if (typeof flowRight != "undefined") {\
+        var compAddOne = function(n) { return n + 1; },\
+            compAddTwo = function(n) { return n + 2; },\
+            compAddThree = function(n) { return n + 3; };\
+        \
+        var _composed = _.flowRight && _.flowRight(compAddThree, compAddTwo, compAddOne),\
+            lodashComposed = lodash.flowRight && lodash.flowRight(compAddThree, compAddTwo, compAddOne);\
+      }\
+      if (typeof countBy != "undefined" || typeof omit != "undefined") {\
+        var wordToNumber = {\
+          "one": 1,\
+          "two": 2,\
+          "three": 3,\
+          "four": 4,\
+          "five": 5,\
+          "six": 6,\
+          "seven": 7,\
+          "eight": 8,\
+          "nine": 9,\
+          "ten": 10,\
+          "eleven": 11,\
+          "twelve": 12,\
+          "thirteen": 13,\
+          "fourteen": 14,\
+          "fifteen": 15,\
+          "sixteen": 16,\
+          "seventeen": 17,\
+          "eighteen": 18,\
+          "nineteen": 19,\
+          "twenty": 20,\
+          "twenty-one": 21,\
+          "twenty-two": 22,\
+          "twenty-three": 23,\
+          "twenty-four": 24,\
+          "twenty-five": 25,\
+          "twenty-six": 26,\
+          "twenty-seven": 27,\
+          "twenty-eight": 28,\
+          "twenty-nine": 29,\
+          "thirty": 30,\
+          "thirty-one": 31,\
+          "thirty-two": 32,\
+          "thirty-three": 33,\
+          "thirty-four": 34,\
+          "thirty-five": 35,\
+          "thirty-six": 36,\
+          "thirty-seven": 37,\
+          "thirty-eight": 38,\
+          "thirty-nine": 39,\
+          "forty": 40\
+        };\
+        \
+        var words = belt.keys(wordToNumber).slice(0, limit);\
+      }\
+      if (typeof flatten != "undefined") {\
+        var _flattenDeep = _.flatten([[1]])[0] !== 1,\
+            lodashFlattenDeep = lodash.flatten([[1]])[0] !== 1;\
+      }\
+      if (typeof isEqual != "undefined") {\
+        var objectOfPrimitives = {\
+          "boolean": true,\
+          "number": 1,\
+          "string": "a"\
+        };\
+        \
+        var objectOfObjects = {\
+          "boolean": new Boolean(true),\
+          "number": new Number(1),\
+          "string": new String("a")\
+        };\
+        \
+        var objectOfObjects2 = {\
+          "boolean": new Boolean(true),\
+          "number": new Number(1),\
+          "string": new String("A")\
+        };\
+        \
+        var object2 = {},\
+            object3 = {},\
+            objects2 = Array(limit),\
+            objects3 = Array(limit),\
+            numbers2 = Array(limit),\
+            numbers3 = Array(limit),\
+            nestedNumbers2 = [1, [2], [3, [[4]]]],\
+            nestedNumbers3 = [1, [2], [3, [[6]]]];\
+        \
+        for (index = 0; index < limit; index++) {\
+          object2["key" + index] = index;\
+          object3["key" + index] = index;\
+          objects2[index] = { "num": index };\
+          objects3[index] = { "num": index };\
+          numbers2[index] = index;\
+          numbers3[index] = index;\
+        }\
+        object3["key" + (limit - 1)] = -1;\
+        objects3[limit - 1].num = -1;\
+        numbers3[limit - 1] = -1;\
+      }\
+      if (typeof matches != "undefined") {\
+        var source = { "num": 9 };\
+        \
+        var _matcher = (_.matches || _.noop)(source),\
+            lodashMatcher = (lodash.matches || lodash.noop)(source);\
+      }\
+      if (typeof multiArrays != "undefined") {\
+        var twentyValues = belt.shuffle(belt.range(20)),\
+            fortyValues = belt.shuffle(belt.range(40)),\
+            hundredSortedValues = belt.range(100),\
+            hundredValues = belt.shuffle(hundredSortedValues),\
+            hundredValues2 = belt.shuffle(hundredValues),\
+            hundredTwentyValues = belt.shuffle(belt.range(120)),\
+            hundredTwentyValues2 = belt.shuffle(hundredTwentyValues),\
+            twoHundredValues = belt.shuffle(belt.range(200)),\
+            twoHundredValues2 = belt.shuffle(twoHundredValues);\
+      }\
+      if (typeof partial != "undefined") {\
+        var func = function(greeting, punctuation) {\
+          return greeting + " fred" + (punctuation || ".");\
+        };\
+        \
+        var _partial = _.partial(func, "hi"),\
+            lodashPartial = lodash.partial(func, "hi");\
+      }\
+      if (typeof template != "undefined") {\
+        var tplData = {\
+          "header1": "Header1",\
+          "header2": "Header2",\
+          "header3": "Header3",\
+          "header4": "Header4",\
+          "header5": "Header5",\
+          "header6": "Header6",\
+          "list": ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10"]\
+        };\
+        \
+        var tpl =\
+          "<div>" +\
+          "<h1 class=\'header1\'><%= header1 %></h1>" +\
+          "<h2 class=\'header2\'><%= header2 %></h2>" +\
+          "<h3 class=\'header3\'><%= header3 %></h3>" +\
+          "<h4 class=\'header4\'><%= header4 %></h4>" +\
+          "<h5 class=\'header5\'><%= header5 %></h5>" +\
+          "<h6 class=\'header6\'><%= header6 %></h6>" +\
+          "<ul class=\'list\'>" +\
+          "<% for (var index = 0, length = list.length; index < length; index++) { %>" +\
+          "<li class=\'item\'><%= list[index] %></li>" +\
+          "<% } %>" +\
+          "</ul>" +\
+          "</div>";\
+        \
+        var tplVerbose =\
+          "<div>" +\
+          "<h1 class=\'header1\'><%= data.header1 %></h1>" +\
+          "<h2 class=\'header2\'><%= data.header2 %></h2>" +\
+          "<h3 class=\'header3\'><%= data.header3 %></h3>" +\
+          "<h4 class=\'header4\'><%= data.header4 %></h4>" +\
+          "<h5 class=\'header5\'><%= data.header5 %></h5>" +\
+          "<h6 class=\'header6\'><%= data.header6 %></h6>" +\
+          "<ul class=\'list\'>" +\
+          "<% for (var index = 0, length = data.list.length; index < length; index++) { %>" +\
+          "<li class=\'item\'><%= data.list[index] %></li>" +\
+          "<% } %>" +\
+          "</ul>" +\
+          "</div>";\
+        \
+        var settingsObject = { "variable": "data" };\
+        \
+        var _tpl = _.template(tpl),\
+            _tplVerbose = _.template(tplVerbose, null, settingsObject);\
+        \
+        var lodashTpl = lodash.template(tpl),\
+            lodashTplVerbose = lodash.template(tplVerbose, null, settingsObject);\
+      }\
+      if (typeof wrap != "undefined") {\
+        var add = function(a, b) {\
+          return a + b;\
+        };\
+        \
+        var average = function(func, a, b) {\
+          return (func(a, b) / 2).toFixed(2);\
+        };\
+        \
+        var _wrapped = _.wrap(add, average);\
+            lodashWrapped = lodash.wrap(add, average);\
+      }\
+      if (typeof zip != "undefined") {\
+        var unzipped = [["a", "b", "c"], [1, 2, 3], [true, false, true]];\
+      }'
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_(...).map(...).filter(...).take(...).value()`')
+      .add(buildName, {
+        'fn': 'lodashChaining.map(square).filter(even).take(100).value()',
+        'teardown': 'function chaining(){}'
+      })
+      .add(otherName, {
+        'fn': '_chaining.map(square).filter(even).take(100).value()',
+        'teardown': 'function chaining(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.assign`')
+      .add(buildName, {
+        'fn': 'lodashAssign({}, { "a": 1, "b": 2, "c": 3 })',
+        'teardown': 'function assign(){}'
+      })
+      .add(otherName, {
+        'fn': '_assign({}, { "a": 1, "b": 2, "c": 3 })',
+        'teardown': 'function assign(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.assign` with multiple sources')
+      .add(buildName, {
+        'fn': 'lodashAssign({}, { "a": 1, "b": 2 }, { "c": 3, "d": 4 })',
+        'teardown': 'function assign(){}'
+      })
+      .add(otherName, {
+        'fn': '_assign({}, { "a": 1, "b": 2 }, { "c": 3, "d": 4 })',
+        'teardown': 'function assign(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.bind` (slow path)')
+      .add(buildName, {
+        'fn': 'lodash.bind(function() { return this.name; }, { "name": "fred" })',
+        'teardown': 'function bind(){}'
+      })
+      .add(otherName, {
+        'fn': '_.bind(function() { return this.name; }, { "name": "fred" })',
+        'teardown': 'function bind(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('bound call with arguments')
+      .add(buildName, {
+        'fn': 'lodashBoundNormal("hi", "!")',
+        'teardown': 'function bind(){}'
+      })
+      .add(otherName, {
+        'fn': '_boundNormal("hi", "!")',
+        'teardown': 'function bind(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('bound and partially applied call with arguments')
+      .add(buildName, {
+        'fn': 'lodashBoundPartial("!")',
+        'teardown': 'function bind(){}'
+      })
+      .add(otherName, {
+        'fn': '_boundPartial("!")',
+        'teardown': 'function bind(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('bound multiple times')
+      .add(buildName, {
+        'fn': 'lodashBoundMultiple()',
+        'teardown': 'function bind(){}'
+      })
+      .add(otherName, {
+        'fn': '_boundMultiple()',
+        'teardown': 'function bind(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.bindAll`')
+      .add(buildName, {
+        'fn': 'lodash.bindAll(bindAllObjects[++bindAllCount], funcNames)',
+        'teardown': 'function bindAll(){}'
+      })
+      .add(otherName, {
+        'fn': '_.bindAll(bindAllObjects[++bindAllCount], funcNames)',
+        'teardown': 'function bindAll(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.clone` with an array')
+      .add(buildName, '\
+        lodash.clone(numbers)'
+      )
+      .add(otherName, '\
+        _.clone(numbers)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.clone` with an object')
+      .add(buildName, '\
+        lodash.clone(object)'
+      )
+      .add(otherName, '\
+        _.clone(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.compact`')
+      .add(buildName, {
+        'fn': 'lodash.compact(uncompacted)',
+        'teardown': 'function compact(){}'
+      })
+      .add(otherName, {
+        'fn': '_.compact(uncompacted)',
+        'teardown': 'function compact(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.countBy` with `callback` iterating an array')
+      .add(buildName, '\
+        lodash.countBy(numbers, function(num) { return num >> 1; })'
+      )
+      .add(otherName, '\
+        _.countBy(numbers, function(num) { return num >> 1; })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.countBy` with `property` name iterating an array')
+      .add(buildName, {
+        'fn': 'lodash.countBy(words, "length")',
+        'teardown': 'function countBy(){}'
+      })
+      .add(otherName, {
+        'fn': '_.countBy(words, "length")',
+        'teardown': 'function countBy(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.countBy` with `callback` iterating an object')
+      .add(buildName, {
+        'fn': 'lodash.countBy(wordToNumber, function(num) { return num >> 1; })',
+        'teardown': 'function countBy(){}'
+      })
+      .add(otherName, {
+        'fn': '_.countBy(wordToNumber, function(num) { return num >> 1; })',
+        'teardown': 'function countBy(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.defaults`')
+      .add(buildName, '\
+        lodash.defaults({ "key2": 2, "key6": 6, "key18": 18 }, object)'
+      )
+      .add(otherName, '\
+        _.defaults({ "key2": 2, "key6": 6, "key18": 18 }, object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.difference`')
+      .add(buildName, '\
+        lodash.difference(numbers, twoNumbers, fourNumbers)'
+      )
+      .add(otherName, '\
+        _.difference(numbers, twoNumbers, fourNumbers)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.difference` iterating 20 and 40 elements')
+      .add(buildName, {
+        'fn': 'lodash.difference(twentyValues, fortyValues)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.difference(twentyValues, fortyValues)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.difference` iterating 200 elements')
+      .add(buildName, {
+        'fn': 'lodash.difference(twoHundredValues, twoHundredValues2)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.difference(twoHundredValues, twoHundredValues2)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.each` iterating an array')
+      .add(buildName, '\
+        var result = [];\
+        lodash.each(numbers, function(num) {\
+          result.push(num * 2);\
+        })'
+      )
+      .add(otherName, '\
+        var result = [];\
+        _.each(numbers, function(num) {\
+          result.push(num * 2);\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.each` iterating an object')
+      .add(buildName, '\
+        var result = [];\
+        lodash.each(object, function(num) {\
+          result.push(num * 2);\
+        })'
+      )
+      .add(otherName, '\
+        var result = [];\
+        _.each(object, function(num) {\
+          result.push(num * 2);\
+        })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.every` iterating an array')
+      .add(buildName, '\
+        lodash.every(numbers, function(num) {\
+          return num < limit;\
+        })'
+      )
+      .add(otherName, '\
+        _.every(numbers, function(num) {\
+          return num < limit;\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.every` iterating an object')
+      .add(buildName, '\
+        lodash.every(object, function(num) {\
+          return num < limit;\
+        })'
+      )
+      .add(otherName, '\
+        _.every(object, function(num) {\
+          return num < limit;\
+        })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.filter` iterating an array')
+      .add(buildName, '\
+        lodash.filter(numbers, function(num) {\
+          return num % 2;\
+        })'
+      )
+      .add(otherName, '\
+        _.filter(numbers, function(num) {\
+          return num % 2;\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.filter` iterating an object')
+      .add(buildName, '\
+        lodash.filter(object, function(num) {\
+          return num % 2\
+        })'
+      )
+      .add(otherName, '\
+        _.filter(object, function(num) {\
+          return num % 2\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.filter` with `_.matches` shorthand')
+      .add(buildName, {
+        'fn': 'lodash.filter(objects, source)',
+        'teardown': 'function matches(){}'
+      })
+      .add(otherName, {
+        'fn': '_.filter(objects, source)',
+        'teardown': 'function matches(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.filter` with `_.matches` predicate')
+      .add(buildName, {
+        'fn': 'lodash.filter(objects, lodashMatcher)',
+        'teardown': 'function matches(){}'
+      })
+      .add(otherName, {
+        'fn': '_.filter(objects, _matcher)',
+        'teardown': 'function matches(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.find` iterating an array')
+      .add(buildName, '\
+        lodash.find(numbers, function(num) {\
+          return num === (limit - 1);\
+        })'
+      )
+      .add(otherName, '\
+        _.find(numbers, function(num) {\
+          return num === (limit - 1);\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.find` iterating an object')
+      .add(buildName, '\
+        lodash.find(object, function(value, key) {\
+          return /\D9$/.test(key);\
+        })'
+      )
+      .add(otherName, '\
+        _.find(object, function(value, key) {\
+          return /\D9$/.test(key);\
+        })'
+      )
+  );
+
+  // Avoid Underscore induced `OutOfMemoryError` in Rhino and Ringo.
+  suites.push(
+    Benchmark.Suite('`_.find` with `_.matches` shorthand')
+      .add(buildName, {
+        'fn': 'lodash.find(objects, source)',
+        'teardown': 'function matches(){}'
+      })
+      .add(otherName, {
+        'fn': '_.find(objects, source)',
+        'teardown': 'function matches(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.flatten`')
+      .add(buildName, {
+        'fn': 'lodash.flatten(nestedNumbers, !lodashFlattenDeep)',
+        'teardown': 'function flatten(){}'
+      })
+      .add(otherName, {
+        'fn': '_.flatten(nestedNumbers, !_flattenDeep)',
+        'teardown': 'function flatten(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.flattenDeep` nested arrays of numbers')
+      .add(buildName, {
+        'fn': 'lodash.flattenDeep(nestedNumbers)',
+        'teardown': 'function flatten(){}'
+      })
+      .add(otherName, {
+        'fn': '_.flattenDeep(nestedNumbers)',
+        'teardown': 'function flatten(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.flattenDeep` nest arrays of objects')
+      .add(buildName, {
+        'fn': 'lodash.flattenDeep(nestedObjects)',
+        'teardown': 'function flatten(){}'
+      })
+      .add(otherName, {
+        'fn': '_.flattenDeep(nestedObjects)',
+        'teardown': 'function flatten(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.flowRight`')
+      .add(buildName, {
+        'fn': 'lodash.flowRight(compAddThree, compAddTwo, compAddOne)',
+        'teardown': 'function flowRight(){}'
+      })
+      .add(otherName, {
+        'fn': '_.flowRight(compAddThree, compAddTwo, compAddOne)',
+        'teardown': 'function flowRight(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('composed call')
+      .add(buildName, {
+        'fn': 'lodashComposed(0)',
+        'teardown': 'function flowRight(){}'
+      })
+      .add(otherName, {
+        'fn': '_composed(0)',
+        'teardown': 'function flowRight(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.functions`')
+      .add(buildName, '\
+        lodash.functions(lodash)'
+      )
+      .add(otherName, '\
+        _.functions(lodash)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.groupBy` with `callback` iterating an array')
+      .add(buildName, '\
+        lodash.groupBy(numbers, function(num) { return num >> 1; })'
+      )
+      .add(otherName, '\
+        _.groupBy(numbers, function(num) { return num >> 1; })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.groupBy` with `property` name iterating an array')
+      .add(buildName, {
+        'fn': 'lodash.groupBy(words, "length")',
+        'teardown': 'function countBy(){}'
+      })
+      .add(otherName, {
+        'fn': '_.groupBy(words, "length")',
+        'teardown': 'function countBy(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.groupBy` with `callback` iterating an object')
+      .add(buildName, {
+        'fn': 'lodash.groupBy(wordToNumber, function(num) { return num >> 1; })',
+        'teardown': 'function countBy(){}'
+      })
+      .add(otherName, {
+        'fn': '_.groupBy(wordToNumber, function(num) { return num >> 1; })',
+        'teardown': 'function countBy(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.includes` searching an array')
+      .add(buildName, '\
+        lodash.includes(numbers, limit - 1)'
+      )
+      .add(otherName, '\
+        _.includes(numbers, limit - 1)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.includes` searching an object')
+      .add(buildName, '\
+        lodash.includes(object, limit - 1)'
+      )
+      .add(otherName, '\
+        _.includes(object, limit - 1)'
+      )
+  );
+
+  if (lodash.includes('ab', 'ab') && _.includes('ab', 'ab')) {
+    suites.push(
+      Benchmark.Suite('`_.includes` searching a string')
+        .add(buildName, '\
+          lodash.includes(strNumbers, "," + (limit - 1))'
+        )
+        .add(otherName, '\
+          _.includes(strNumbers, "," + (limit - 1))'
+        )
+    );
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.indexOf`')
+      .add(buildName, {
+        'fn': 'lodash.indexOf(hundredSortedValues, 99)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.indexOf(hundredSortedValues, 99)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.intersection`')
+      .add(buildName, '\
+        lodash.intersection(numbers, twoNumbers, fourNumbers)'
+      )
+      .add(otherName, '\
+        _.intersection(numbers, twoNumbers, fourNumbers)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.intersection` iterating 120 elements')
+      .add(buildName, {
+        'fn': 'lodash.intersection(hundredTwentyValues, hundredTwentyValues2)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.intersection(hundredTwentyValues, hundredTwentyValues2)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.invert`')
+      .add(buildName, '\
+        lodash.invert(object)'
+      )
+      .add(otherName, '\
+        _.invert(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.invokeMap` iterating an array')
+      .add(buildName, '\
+        lodash.invokeMap(numbers, "toFixed")'
+      )
+      .add(otherName, '\
+        _.invokeMap(numbers, "toFixed")'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.invokeMap` with arguments iterating an array')
+      .add(buildName, '\
+        lodash.invokeMap(numbers, "toFixed", 1)'
+      )
+      .add(otherName, '\
+        _.invokeMap(numbers, "toFixed", 1)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.invokeMap` with a function for `path` iterating an array')
+      .add(buildName, '\
+        lodash.invokeMap(numbers, Number.prototype.toFixed, 1)'
+      )
+      .add(otherName, '\
+        _.invokeMap(numbers, Number.prototype.toFixed, 1)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.invokeMap` iterating an object')
+      .add(buildName, '\
+        lodash.invokeMap(object, "toFixed", 1)'
+      )
+      .add(otherName, '\
+        _.invokeMap(object, "toFixed", 1)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.isEqual` comparing primitives')
+      .add(buildName, {
+        'fn': '\
+          lodash.isEqual(1, "1");\
+          lodash.isEqual(1, 1)',
+        'teardown': 'function isEqual(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.isEqual(1, "1");\
+          _.isEqual(1, 1);',
+        'teardown': 'function isEqual(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.isEqual` comparing primitives and their object counterparts (edge case)')
+      .add(buildName, {
+        'fn': '\
+          lodash.isEqual(objectOfPrimitives, objectOfObjects);\
+          lodash.isEqual(objectOfPrimitives, objectOfObjects2)',
+        'teardown': 'function isEqual(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.isEqual(objectOfPrimitives, objectOfObjects);\
+          _.isEqual(objectOfPrimitives, objectOfObjects2)',
+        'teardown': 'function isEqual(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.isEqual` comparing arrays')
+      .add(buildName, {
+        'fn': '\
+          lodash.isEqual(numbers, numbers2);\
+          lodash.isEqual(numbers2, numbers3)',
+        'teardown': 'function isEqual(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.isEqual(numbers, numbers2);\
+          _.isEqual(numbers2, numbers3)',
+        'teardown': 'function isEqual(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.isEqual` comparing nested arrays')
+      .add(buildName, {
+        'fn': '\
+          lodash.isEqual(nestedNumbers, nestedNumbers2);\
+          lodash.isEqual(nestedNumbers2, nestedNumbers3)',
+        'teardown': 'function isEqual(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.isEqual(nestedNumbers, nestedNumbers2);\
+          _.isEqual(nestedNumbers2, nestedNumbers3)',
+        'teardown': 'function isEqual(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.isEqual` comparing arrays of objects')
+      .add(buildName, {
+        'fn': '\
+          lodash.isEqual(objects, objects2);\
+          lodash.isEqual(objects2, objects3)',
+        'teardown': 'function isEqual(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.isEqual(objects, objects2);\
+          _.isEqual(objects2, objects3)',
+        'teardown': 'function isEqual(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.isEqual` comparing objects')
+      .add(buildName, {
+        'fn': '\
+          lodash.isEqual(object, object2);\
+          lodash.isEqual(object2, object3)',
+        'teardown': 'function isEqual(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.isEqual(object, object2);\
+          _.isEqual(object2, object3)',
+        'teardown': 'function isEqual(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.isArguments`, `_.isDate`, `_.isFunction`, `_.isNumber`, `_.isObject`, `_.isRegExp`')
+      .add(buildName, '\
+        lodash.isArguments(arguments);\
+        lodash.isArguments(object);\
+        lodash.isDate(date);\
+        lodash.isDate(object);\
+        lodash.isFunction(lodash);\
+        lodash.isFunction(object);\
+        lodash.isNumber(1);\
+        lodash.isNumber(object);\
+        lodash.isObject(object);\
+        lodash.isObject(1);\
+        lodash.isRegExp(regexp);\
+        lodash.isRegExp(object)'
+      )
+      .add(otherName, '\
+        _.isArguments(arguments);\
+        _.isArguments(object);\
+        _.isDate(date);\
+        _.isDate(object);\
+        _.isFunction(_);\
+        _.isFunction(object);\
+        _.isNumber(1);\
+        _.isNumber(object);\
+        _.isObject(object);\
+        _.isObject(1);\
+        _.isRegExp(regexp);\
+        _.isRegExp(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.keys` (uses native `Object.keys` if available)')
+      .add(buildName, '\
+        lodash.keys(object)'
+      )
+      .add(otherName, '\
+        _.keys(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.lastIndexOf`')
+      .add(buildName, {
+        'fn': 'lodash.lastIndexOf(hundredSortedValues, 0)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.lastIndexOf(hundredSortedValues, 0)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.map` iterating an array')
+      .add(buildName, '\
+        lodash.map(objects, function(value) {\
+          return value.num;\
+        })'
+      )
+      .add(otherName, '\
+        _.map(objects, function(value) {\
+          return value.num;\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.map` iterating an object')
+      .add(buildName, '\
+        lodash.map(object, function(value) {\
+          return value;\
+        })'
+      )
+      .add(otherName, '\
+        _.map(object, function(value) {\
+          return value;\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.map` with `_.property` shorthand')
+      .add(buildName, '\
+        lodash.map(objects, "num")'
+      )
+      .add(otherName, '\
+        _.map(objects, "num")'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.max`')
+      .add(buildName, '\
+        lodash.max(numbers)'
+      )
+      .add(otherName, '\
+        _.max(numbers)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.min`')
+      .add(buildName, '\
+        lodash.min(numbers)'
+      )
+      .add(otherName, '\
+        _.min(numbers)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.omit` iterating 20 properties, omitting 2 keys')
+      .add(buildName, '\
+        lodash.omit(object, "key6", "key13")'
+      )
+      .add(otherName, '\
+        _.omit(object, "key6", "key13")'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.omit` iterating 40 properties, omitting 20 keys')
+      .add(buildName, {
+        'fn': 'lodash.omit(wordToNumber, words)',
+        'teardown': 'function omit(){}'
+      })
+      .add(otherName, {
+        'fn': '_.omit(wordToNumber, words)',
+        'teardown': 'function omit(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.partial` (slow path)')
+      .add(buildName, {
+        'fn': 'lodash.partial(function(greeting) { return greeting + " " + this.name; }, "hi")',
+        'teardown': 'function partial(){}'
+      })
+      .add(otherName, {
+        'fn': '_.partial(function(greeting) { return greeting + " " + this.name; }, "hi")',
+        'teardown': 'function partial(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('partially applied call with arguments')
+      .add(buildName, {
+        'fn': 'lodashPartial("!")',
+        'teardown': 'function partial(){}'
+      })
+      .add(otherName, {
+        'fn': '_partial("!")',
+        'teardown': 'function partial(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.partition` iterating an array')
+      .add(buildName, '\
+        lodash.partition(numbers, function(num) {\
+          return num % 2;\
+        })'
+      )
+      .add(otherName, '\
+        _.partition(numbers, function(num) {\
+          return num % 2;\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.partition` iterating an object')
+      .add(buildName, '\
+        lodash.partition(object, function(num) {\
+          return num % 2;\
+        })'
+      )
+      .add(otherName, '\
+        _.partition(object, function(num) {\
+          return num % 2;\
+        })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.pick`')
+      .add(buildName, '\
+        lodash.pick(object, "key6", "key13")'
+      )
+      .add(otherName, '\
+        _.pick(object, "key6", "key13")'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.reduce` iterating an array')
+      .add(buildName, '\
+        lodash.reduce(numbers, function(result, value, index) {\
+          result[index] = value;\
+          return result;\
+        }, {})'
+      )
+      .add(otherName, '\
+        _.reduce(numbers, function(result, value, index) {\
+          result[index] = value;\
+          return result;\
+        }, {})'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.reduce` iterating an object')
+      .add(buildName, '\
+        lodash.reduce(object, function(result, value, key) {\
+          result.push(key, value);\
+          return result;\
+        }, [])'
+      )
+      .add(otherName, '\
+        _.reduce(object, function(result, value, key) {\
+          result.push(key, value);\
+          return result;\
+        }, [])'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.reduceRight` iterating an array')
+      .add(buildName, '\
+        lodash.reduceRight(numbers, function(result, value, index) {\
+          result[index] = value;\
+          return result;\
+        }, {})'
+      )
+      .add(otherName, '\
+        _.reduceRight(numbers, function(result, value, index) {\
+          result[index] = value;\
+          return result;\
+        }, {})'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.reduceRight` iterating an object')
+      .add(buildName, '\
+        lodash.reduceRight(object, function(result, value, key) {\
+          result.push(key, value);\
+          return result;\
+        }, [])'
+      )
+      .add(otherName, '\
+        _.reduceRight(object, function(result, value, key) {\
+          result.push(key, value);\
+          return result;\
+        }, [])'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.reject` iterating an array')
+      .add(buildName, '\
+        lodash.reject(numbers, function(num) {\
+          return num % 2;\
+        })'
+      )
+      .add(otherName, '\
+        _.reject(numbers, function(num) {\
+          return num % 2;\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.reject` iterating an object')
+      .add(buildName, '\
+        lodash.reject(object, function(num) {\
+          return num % 2;\
+        })'
+      )
+      .add(otherName, '\
+        _.reject(object, function(num) {\
+          return num % 2;\
+        })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sampleSize`')
+      .add(buildName, '\
+        lodash.sampleSize(numbers, limit / 2)'
+      )
+      .add(otherName, '\
+        _.sampleSize(numbers, limit / 2)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.shuffle`')
+      .add(buildName, '\
+        lodash.shuffle(numbers)'
+      )
+      .add(otherName, '\
+        _.shuffle(numbers)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.size` with an object')
+      .add(buildName, '\
+        lodash.size(object)'
+      )
+      .add(otherName, '\
+        _.size(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.some` iterating an array')
+      .add(buildName, '\
+        lodash.some(numbers, function(num) {\
+          return num == (limit - 1);\
+        })'
+      )
+      .add(otherName, '\
+        _.some(numbers, function(num) {\
+          return num == (limit - 1);\
+        })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.some` iterating an object')
+      .add(buildName, '\
+        lodash.some(object, function(num) {\
+          return num == (limit - 1);\
+        })'
+      )
+      .add(otherName, '\
+        _.some(object, function(num) {\
+          return num == (limit - 1);\
+        })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sortBy` with `callback`')
+      .add(buildName, '\
+        lodash.sortBy(numbers, function(num) { return Math.sin(num); })'
+      )
+      .add(otherName, '\
+        _.sortBy(numbers, function(num) { return Math.sin(num); })'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.sortBy` with `property` name')
+      .add(buildName, {
+        'fn': 'lodash.sortBy(words, "length")',
+        'teardown': 'function countBy(){}'
+      })
+      .add(otherName, {
+        'fn': '_.sortBy(words, "length")',
+        'teardown': 'function countBy(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sortedIndex`')
+      .add(buildName, '\
+        lodash.sortedIndex(numbers, limit)'
+      )
+      .add(otherName, '\
+        _.sortedIndex(numbers, limit)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sortedIndexBy`')
+      .add(buildName, {
+        'fn': '\
+          lodash.sortedIndexBy(words, "twenty-five", function(value) {\
+            return wordToNumber[value];\
+          })',
+        'teardown': 'function countBy(){}'
+      })
+      .add(otherName, {
+        'fn': '\
+          _.sortedIndexBy(words, "twenty-five", function(value) {\
+            return wordToNumber[value];\
+          })',
+        'teardown': 'function countBy(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sortedIndexOf`')
+      .add(buildName, {
+        'fn': 'lodash.sortedIndexOf(hundredSortedValues, 99)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.sortedIndexOf(hundredSortedValues, 99)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sortedLastIndexOf`')
+      .add(buildName, {
+        'fn': 'lodash.sortedLastIndexOf(hundredSortedValues, 0)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.sortedLastIndexOf(hundredSortedValues, 0)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.sum`')
+      .add(buildName, '\
+        lodash.sum(numbers)'
+      )
+      .add(otherName, '\
+        _.sum(numbers)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.template` (slow path)')
+      .add(buildName, {
+        'fn': 'lodash.template(tpl)(tplData)',
+        'teardown': 'function template(){}'
+      })
+      .add(otherName, {
+        'fn': '_.template(tpl)(tplData)',
+        'teardown': 'function template(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('compiled template')
+      .add(buildName, {
+        'fn': 'lodashTpl(tplData)',
+        'teardown': 'function template(){}'
+      })
+      .add(otherName, {
+        'fn': '_tpl(tplData)',
+        'teardown': 'function template(){}'
+      })
+  );
+
+  suites.push(
+    Benchmark.Suite('compiled template without a with-statement')
+      .add(buildName, {
+        'fn': 'lodashTplVerbose(tplData)',
+        'teardown': 'function template(){}'
+      })
+      .add(otherName, {
+        'fn': '_tplVerbose(tplData)',
+        'teardown': 'function template(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.times`')
+      .add(buildName, '\
+        var result = [];\
+        lodash.times(limit, function(n) { result.push(n); })'
+      )
+      .add(otherName, '\
+        var result = [];\
+        _.times(limit, function(n) { result.push(n); })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.toArray` with an array (edge case)')
+      .add(buildName, '\
+        lodash.toArray(numbers)'
+      )
+      .add(otherName, '\
+        _.toArray(numbers)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.toArray` with an object')
+      .add(buildName, '\
+        lodash.toArray(object)'
+      )
+      .add(otherName, '\
+        _.toArray(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.toPairs`')
+      .add(buildName, '\
+        lodash.toPairs(object)'
+      )
+      .add(otherName, '\
+        _.toPairs(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.unescape` string without html entities')
+      .add(buildName, '\
+        lodash.unescape("`&`, `<`, `>`, `\\"`, and `\'`")'
+      )
+      .add(otherName, '\
+        _.unescape("`&`, `<`, `>`, `\\"`, and `\'`")'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.unescape` string with html entities')
+      .add(buildName, '\
+        lodash.unescape("`&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;`")'
+      )
+      .add(otherName, '\
+        _.unescape("`&amp;`, `&lt;`, `&gt;`, `&quot;`, and `&#39;`")'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.union`')
+      .add(buildName, '\
+        lodash.union(numbers, twoNumbers, fourNumbers)'
+      )
+      .add(otherName, '\
+        _.union(numbers, twoNumbers, fourNumbers)'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.union` iterating an array of 200 elements')
+      .add(buildName, {
+        'fn': 'lodash.union(hundredValues, hundredValues2)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.union(hundredValues, hundredValues2)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.uniq`')
+      .add(buildName, '\
+        lodash.uniq(numbers.concat(twoNumbers, fourNumbers))'
+      )
+      .add(otherName, '\
+        _.uniq(numbers.concat(twoNumbers, fourNumbers))'
+      )
+  );
+
+  suites.push(
+    Benchmark.Suite('`_.uniq` iterating an array of 200 elements')
+      .add(buildName, {
+        'fn': 'lodash.uniq(twoHundredValues)',
+        'teardown': 'function multiArrays(){}'
+      })
+      .add(otherName, {
+        'fn': '_.uniq(twoHundredValues)',
+        'teardown': 'function multiArrays(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.uniqBy`')
+      .add(buildName, '\
+        lodash.uniqBy(numbers.concat(twoNumbers, fourNumbers), function(num) {\
+          return num % 2;\
+        })'
+      )
+      .add(otherName, '\
+        _.uniqBy(numbers.concat(twoNumbers, fourNumbers), function(num) {\
+          return num % 2;\
+        })'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.values`')
+      .add(buildName, '\
+        lodash.values(object)'
+      )
+      .add(otherName, '\
+        _.values(object)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.without`')
+      .add(buildName, '\
+        lodash.without(numbers, 9, 12, 14, 15)'
+      )
+      .add(otherName, '\
+        _.without(numbers, 9, 12, 14, 15)'
+      )
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.wrap` result called')
+      .add(buildName, {
+        'fn': 'lodashWrapped(2, 5)',
+        'teardown': 'function wrap(){}'
+      })
+      .add(otherName, {
+        'fn': '_wrapped(2, 5)',
+        'teardown': 'function wrap(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  suites.push(
+    Benchmark.Suite('`_.zip`')
+      .add(buildName, {
+        'fn': 'lodash.zip.apply(lodash, unzipped)',
+        'teardown': 'function zip(){}'
+      })
+      .add(otherName, {
+        'fn': '_.zip.apply(_, unzipped)',
+        'teardown': 'function zip(){}'
+      })
+  );
+
+  /*--------------------------------------------------------------------------*/
+
+  if (Benchmark.platform + '') {
+    log(Benchmark.platform);
+  }
+  // Expose `run` to be called later when executing in a browser.
+  if (document) {
+    root.run = run;
+  } else {
+    run();
+  }
+}.call(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/asset/test-ui.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/asset/test-ui.js
new file mode 100644
index 0000000..24f087a
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/asset/test-ui.js
@@ -0,0 +1,170 @@
+;(function(window) {
+  'use strict';
+
+  /** The base path of the lodash builds. */
+  var basePath = '../';
+
+  /** The lodash build to load. */
+  var build = (build = /build=([^&]+)/.exec(location.search)) && decodeURIComponent(build[1]);
+
+  /** The module loader to use. */
+  var loader = (loader = /loader=([^&]+)/.exec(location.search)) && decodeURIComponent(loader[1]);
+
+  /** The `ui` object. */
+  var ui = {};
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Registers an event listener on an element.
+   *
+   * @private
+   * @param {Element} element The element.
+   * @param {string} eventName The name of the event.
+   * @param {Function} handler The event handler.
+   * @returns {Element} The element.
+   */
+  function addListener(element, eventName, handler) {
+    if (typeof element.addEventListener != 'undefined') {
+      element.addEventListener(eventName, handler, false);
+    } else if (typeof element.attachEvent != 'undefined') {
+      element.attachEvent('on' + eventName, handler);
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Initialize controls.
+  addListener(window, 'load', function() {
+    function eventHandler(event) {
+      var buildIndex = buildList.selectedIndex,
+          loaderIndex = loaderList.selectedIndex,
+          search = location.search.replace(/^\?|&?(?:build|loader)=[^&]*&?/g, '');
+
+      if (event.stopPropagation) {
+        event.stopPropagation();
+      } else {
+        event.cancelBubble = true;
+      }
+      location.href =
+        location.href.split('?')[0] + '?' +
+        (search ? search + '&' : '') +
+        'build=' + (buildIndex < 0 ? build : buildList[buildIndex].value) + '&' +
+        'loader=' + (loaderIndex < 0 ? loader : loaderList[loaderIndex].value);
+    }
+
+    function init() {
+      var toolbar = document.getElementById('qunit-testrunner-toolbar');
+      if (!toolbar) {
+        setTimeout(init, 15);
+        return;
+      }
+      toolbar.appendChild(span1);
+      toolbar.appendChild(span2);
+
+      buildList.selectedIndex = (function() {
+        switch (build) {
+          case 'lodash':            return 1;
+          case 'lodash-core-dev':   return 2;
+          case 'lodash-core':       return 3;
+          case 'lodash-dev':
+          case null:                return 0;
+        }
+        return -1;
+      }());
+
+      loaderList.selectedIndex = (function() {
+        switch (loader) {
+          case 'curl':      return 1;
+          case 'dojo':      return 2;
+          case 'requirejs': return 3;
+          case 'none':
+          case null:        return 0;
+        }
+        return -1;
+      }());
+
+      addListener(buildList, 'change', eventHandler);
+      addListener(loaderList, 'change', eventHandler);
+    }
+
+    var span1 = document.createElement('span');
+    span1.style.cssText = 'float:right';
+    span1.innerHTML =
+      '<label for="qunit-build">Build: </label>' +
+      '<select id="qunit-build">' +
+      '<option value="lodash-dev">lodash (development)</option>' +
+      '<option value="lodash">lodash (production)</option>' +
+      '<option value="lodash-core-dev">lodash-core (development)</option>' +
+      '<option value="lodash-core">lodash-core (production)</option>' +
+      '</select>';
+
+    var span2 = document.createElement('span');
+    span2.style.cssText = 'float:right';
+    span2.innerHTML =
+      '<label for="qunit-loader">Loader: </label>' +
+      '<select id="qunit-loader">' +
+      '<option value="none">None</option>' +
+      '<option value="curl">Curl</option>' +
+      '<option value="dojo">Dojo</option>' +
+      '<option value="requirejs">RequireJS</option>' +
+      '</select>';
+
+    var buildList = span1.lastChild,
+        loaderList = span2.lastChild;
+
+    setTimeout(function() {
+      ui.timing.loadEventEnd = +new Date;
+    }, 1);
+
+    init();
+  });
+
+  // The lodash build file path.
+  ui.buildPath = (function() {
+    var result;
+    switch (build) {
+      case 'lodash':            result = 'dist/lodash.min.js'; break;
+      case 'lodash-core-dev':   result = 'dist/lodash.core.js'; break;
+      case 'lodash-core':       result = 'dist/lodash.core.min.js'; break;
+      case null:                build  = 'lodash-dev';
+      case 'lodash-dev':        result = 'lodash.js'; break;
+      default:                  return build;
+    }
+    return basePath + result;
+  }());
+
+  // The module loader file path.
+  ui.loaderPath = (function() {
+    var result;
+    switch (loader) {
+      case 'curl':      result = 'node_modules/curl-amd/dist/curl-kitchen-sink/curl.js'; break;
+      case 'dojo':      result = 'node_modules/dojo/dojo.js'; break;
+      case 'requirejs': result = 'node_modules/requirejs/require.js'; break;
+      case null:        loader = 'none'; return '';
+      default:          return loader;
+    }
+    return basePath + result;
+  }());
+
+  // Used to indicate testing a core build.
+  ui.isCore = /\bcore(\.min)?\.js\b/.test(ui.buildPath);
+
+  // Used to indicate testing a foreign file.
+  ui.isForeign = RegExp('^(\\w+:)?//').test(build);
+
+  // Used to indicate testing a modularized build.
+  ui.isModularize = /\b(?:amd|commonjs|es|node|npm|(index|main)\.js)\b/.test([location.pathname, location.search]);
+
+  // Used to indicate testing in Sauce Labs' automated test cloud.
+  ui.isSauceLabs = location.port == '9001';
+
+  // Used to indicate that lodash is in strict mode.
+  ui.isStrict = /\bes\b/.test([location.pathname, location.search]);
+
+  ui.urlParams = { 'build': build, 'loader': loader };
+  ui.timing = { 'loadEventEnd': 0 };
+
+  window.ui = ui;
+
+}(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/asset/worker.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/asset/worker.js
new file mode 100644
index 0000000..8ccffa8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/asset/worker.js
@@ -0,0 +1,15 @@
+self.console || (self.console = { 'log': function() {} });
+
+addEventListener('message', function(e) {
+  if (e.data) {
+    try {
+      importScripts('../' + e.data);
+    } catch (e) {
+      var lineNumber = e.lineNumber,
+          message = (lineNumber == null ? '' : (lineNumber + ': ')) + e.message;
+
+      self._ = { 'VERSION': message };
+    }
+    postMessage(_.VERSION);
+  }
+}, false);
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/backbone.html b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/backbone.html
new file mode 100644
index 0000000..dd3df9f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/backbone.html
@@ -0,0 +1,170 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Backbone Test Suite</title>
+    <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
+  </head>
+  <body>
+    <script>
+      // Avoid reporting tests to Sauce Labs when script errors occur.
+      if (location.port == '9001') {
+        window.onerror = function(message) {
+          if (window.QUnit) {
+            QUnit.config.done.length = 0;
+          }
+          global_test_results = { 'message': message };
+        };
+      }
+    </script>
+    <script src="../node_modules/qunitjs/qunit/qunit.js"></script>
+    <script src="../node_modules/qunit-extras/qunit-extras.js"></script>
+    <script src="../vendor/json-js/json2.js"></script>
+    <script src="../node_modules/platform/platform.js"></script>
+    <script src="./asset/test-ui.js"></script>
+    <script src="../lodash.js"></script>
+    <script>
+      QUnit.config.asyncRetries = 10;
+      QUnit.config.hidepassed = true;
+
+      var mixinPrereqs = (function() {
+        var aliasToReal = {
+          'indexBy': 'keyBy',
+          'invoke': 'invokeMap'
+        };
+
+        var keyMap = {
+          'rest': 'tail'
+        };
+
+        var lodash = _.noConflict();
+
+        return function(_) {
+          lodash.defaultsDeep(_, { 'templateSettings': lodash.templateSettings });
+          lodash.mixin(_, lodash.pick(lodash, lodash.difference([
+            'countBy',
+            'debounce',
+            'difference',
+            'find',
+            'findIndex',
+            'findLastIndex',
+            'groupBy',
+            'includes',
+            'invert',
+            'invokeMap',
+            'keyBy',
+            'omit',
+            'partition',
+            'reduceRight',
+            'reject',
+            'sample',
+            'without'
+          ], lodash.functions(_))));
+
+          lodash.forOwn(keyMap, function(realName, otherName) {
+            _[otherName] = lodash[realName];
+            _.prototype[otherName] = lodash.prototype[realName];
+          });
+
+          lodash.forOwn(aliasToReal, function(realName, alias) {
+            _[alias] = _[realName];
+            _.prototype[alias] = _.prototype[realName];
+          });
+        };
+      }());
+
+      // Load prerequisite scripts.
+      document.write(ui.urlParams.loader == 'none'
+        ? '<script src="' + ui.buildPath + '"><\/script>'
+        : '<script data-dojo-config="async:1" src="' + ui.loaderPath + '"><\/script>'
+      );
+    </script>
+    <script>
+      if (ui.urlParams.loader == 'none') {
+        mixinPrereqs(_);
+        document.write([
+          '<script src="../node_modules/jquery/dist/jquery.js"><\/script>',
+          '<script src="../vendor/backbone/backbone.js"><\/script>',
+          '<script src="../vendor/backbone/test/setup/dom-setup.js"><\/script>',
+          '<script src="../vendor/backbone/test/setup/environment.js"><\/script>',
+          '<script src="../vendor/backbone/test/noconflict.js"><\/script>',
+          '<script src="../vendor/backbone/test/events.js"><\/script>',
+          '<script src="../vendor/backbone/test/model.js"><\/script>',
+          '<script src="../vendor/backbone/test/collection.js"><\/script>',
+          '<script src="../vendor/backbone/test/router.js"><\/script>',
+          '<script src="../vendor/backbone/test/view.js"><\/script>',
+          '<script src="../vendor/backbone/test/sync.js"><\/script>'
+        ].join('\n'));
+      }
+    </script>
+    <script>
+      (function() {
+        if (window.curl) {
+          curl.config({ 'apiName': 'require' });
+        }
+        if (!window.require) {
+          return;
+        }
+        var reBasename = /[\w.-]+$/,
+            basePath = ('//' + location.host + location.pathname.replace(reBasename, '')).replace(/\btest\/$/, ''),
+            modulePath = ui.buildPath.replace(/\.js$/, ''),
+            locationPath = modulePath.replace(reBasename, '').replace(/^\/|\/$/g, ''),
+            moduleMain = modulePath.match(reBasename)[0],
+            uid = +new Date;
+
+        function getConfig() {
+          var result = {
+            'baseUrl': './',
+            'urlArgs': 't=' + uid++,
+            'waitSeconds': 0,
+            'paths': {
+              'backbone': '../vendor/backbone/backbone',
+              'jquery': '../node_modules/jquery/dist/jquery'
+            },
+            'packages': [{
+              'name': 'test',
+              'location': '../vendor/backbone/test',
+              'config': {
+                // Work around no global being exported.
+                'exports': 'QUnit',
+                'loader': 'curl/loader/legacy'
+              }
+            }]
+          };
+
+          if (ui.isModularize) {
+            result.packages.push({
+              'name': 'underscore',
+              'location': locationPath,
+              'main': moduleMain
+            });
+          } else {
+            result.paths.underscore = modulePath;
+          }
+          return result;
+        }
+
+        QUnit.config.autostart = false;
+
+        require(getConfig(), ['underscore'], function(lodash) {
+          mixinPrereqs(lodash);
+          require(getConfig(), ['backbone'], function() {
+            require(getConfig(), [
+              'test/setup/dom-setup',
+              'test/setup/environment',
+              'test/noconflict',
+              'test/events',
+              'test/model',
+              'test/collection',
+              'test/router',
+              'test/view',
+              'test/sync'
+            ], function() {
+              QUnit.start();
+            });
+          });
+        });
+      }());
+    </script>
+  </body>
+</html>
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/fp.html b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/fp.html
new file mode 100644
index 0000000..7122966
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/fp.html
@@ -0,0 +1,41 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>lodash-fp Test Suite</title>
+    <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
+  </head>
+  <body>
+    <script>
+      // Avoid reporting tests to Sauce Labs when script errors occur.
+      if (location.port == '9001') {
+        window.onerror = function(message) {
+          if (window.QUnit) {
+            QUnit.config.done.length = 0;
+          }
+          global_test_results = { 'message': message };
+        };
+      }
+    </script>
+    <script src="../lodash.js"></script>
+    <script src="../dist/lodash.fp.js"></script>
+    <script src="../dist/mapping.fp.js"></script>
+    <script src="../node_modules/qunitjs/qunit/qunit.js"></script>
+    <script src="../node_modules/qunit-extras/qunit-extras.js"></script>
+    <script src="../node_modules/platform/platform.js"></script>
+    <script src="./test-fp.js"></script>
+    <div id="qunit"></div>
+    <script>
+      // Set a more readable browser name.
+      window.onload = function() {
+        var timeoutId = setInterval(function() {
+          var ua = document.getElementById('qunit-userAgent');
+          if (ua) {
+            ua.innerHTML = platform;
+            clearInterval(timeoutId);
+          }
+        }, 16);
+      };
+    </script>
+  </body>
+</html>
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/index.html b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/index.html
new file mode 100644
index 0000000..aee3942
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/index.html
@@ -0,0 +1,351 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>lodash Test Suite</title>
+    <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
+    <style>
+      #exports, #module {
+        display: none;
+      }
+    </style>
+  </head>
+  <body>
+    <script>
+      // Avoid reporting tests to Sauce Labs when script errors occur.
+      if (location.port == '9001') {
+        window.onerror = function(message) {
+          if (window.QUnit) {
+            QUnit.config.done.length = 0;
+          }
+          global_test_results = { 'message': message };
+        };
+      }
+    </script>
+    <script src="../node_modules/lodash/lodash.js"></script>
+    <script>var lodashStable = _.noConflict();</script>
+    <script src="../node_modules/qunitjs/qunit/qunit.js"></script>
+    <script src="../node_modules/qunit-extras/qunit-extras.js"></script>
+    <script src="../node_modules/platform/platform.js"></script>
+    <script src="./asset/test-ui.js"></script>
+    <div id="qunit"></div>
+    <div id="exports"></div>
+    <div id="module"></div>
+    <script>
+      function setProperty(object, key, value) {
+        try {
+          Object.defineProperty(object, key, {
+            'configurable': true,
+            'enumerable': false,
+            'writable': true,
+            'value': value
+          });
+        } catch (e) {
+          object[key] = value;
+        }
+        return object;
+      }
+
+      function addBizarroMethods() {
+        var funcProto = Function.prototype,
+            objectProto = Object.prototype;
+
+        var hasOwnProperty = objectProto.hasOwnProperty,
+            fnToString = funcProto.toString,
+            nativeString = fnToString.call(objectProto.toString),
+            noop = function() {},
+            propertyIsEnumerable = objectProto.propertyIsEnumerable,
+            reToString = /toString/g;
+
+        function constant(value) {
+          return function() {
+            return value;
+          };
+        }
+
+        function createToString(funcName) {
+          return constant(nativeString.replace(reToString, funcName));
+        }
+
+        // Allow bypassing native checks.
+        setProperty(funcProto, 'toString', (function() {
+          function wrapper() {
+            setProperty(funcProto, 'toString', fnToString);
+            var result = hasOwnProperty.call(this, 'toString') ? this.toString() : fnToString.call(this);
+            setProperty(funcProto, 'toString', wrapper);
+            return result;
+          }
+          return wrapper;
+        }()));
+
+        // Add prototype extensions.
+        funcProto._method = noop;
+
+        // Set bad shims.
+        setProperty(Object, '_create', Object.create);
+        setProperty(Object, 'create', (function() {
+          function object() {}
+          return function(prototype) {
+            if (prototype === Object(prototype)) {
+              object.prototype = prototype;
+              var result = new object;
+              object.prototype = undefined;
+            }
+            return result || {};
+          };
+        }()));
+
+        setProperty(Object, '_getOwnPropertySymbols', Object.getOwnPropertySymbols);
+        setProperty(Object, 'getOwnPropertySymbols', undefined);
+
+        setProperty(objectProto, '_propertyIsEnumerable', propertyIsEnumerable);
+        setProperty(objectProto, 'propertyIsEnumerable', function(key) {
+          return !(key == 'valueOf' && this && this.valueOf === 1) && _propertyIsEnumerable.call(this, key);
+        });
+
+        setProperty(window, '_Map', window.Map);
+        if (_Map) {
+          setProperty(window, 'Map', (function(Map) {
+            var count = 0;
+            return function() {
+              if (count++) {
+                return new Map;
+              }
+              var result = {};
+              setProperty(window, 'Map', Map);
+              return result;
+            };
+          }(_Map)));
+
+          setProperty(Map, 'toString', createToString('Map'));
+        }
+        setProperty(window, '_Promise', window.Promise);
+        setProperty(window, 'Promise', noop);
+
+        setProperty(window, '_Set', window.Set);
+        setProperty(window, 'Set', noop);
+
+        setProperty(window, '_Symbol', window.Symbol);
+        setProperty(window, 'Symbol', undefined);
+
+        setProperty(window, '_WeakMap', window.WeakMap);
+        setProperty(window, 'WeakMap', noop);
+
+        // Fake `WinRTError`.
+        setProperty(window, 'WinRTError', Error);
+
+        // Fake free variable `global`.
+        setProperty(window, 'exports', window);
+        setProperty(window, 'global', window);
+        setProperty(window, 'module', {});
+      }
+
+      function removeBizarroMethods() {
+        var funcProto = Function.prototype,
+            objectProto = Object.prototype;
+
+        setProperty(objectProto, 'propertyIsEnumerable', objectProto._propertyIsEnumerable);
+
+        if (Object._create) {
+          Object.create = Object._create;
+        } else {
+          delete Object.create;
+        }
+        if (Object._getOwnPropertySymbols) {
+          Object.getOwnPropertySymbols = Object._getOwnPropertySymbols;
+        } else {
+          delete Object.getOwnPropertySymbols;
+        }
+        if (_Map) {
+          Map = _Map;
+        } else {
+          setProperty(window, 'Map', undefined);
+        }
+        if (_Promise) {
+          Promise = _Promise;
+        } else {
+          setProperty(window, 'Promise', undefined);
+        }
+        if (_Set) {
+          Set = _Set;
+        } else {
+          setProperty(window, 'Set', undefined);
+        }
+        if (_Symbol) {
+          Symbol = _Symbol;
+        }
+        if (_WeakMap) {
+          WeakMap = _WeakMap;
+        } else {
+          setProperty(window, 'WeakMap', undefined);
+        }
+        setProperty(window, '_Map', undefined);
+        setProperty(window, '_Promise', undefined);
+        setProperty(window, '_Set', undefined);
+        setProperty(window, '_Symbol', undefined);
+        setProperty(window, '_WeakMap', undefined);
+
+        setProperty(window, 'WinRTError', undefined);
+
+        setProperty(window, 'exports', document.getElementById('exports'));
+        setProperty(window, 'global', undefined);
+        setProperty(window, 'module', document.getElementById('module'));
+
+        delete funcProto._method;
+        delete Object._create;
+        delete Object._getOwnPropertySymbols;
+        delete objectProto._propertyIsEnumerable;
+      }
+
+      // Load lodash to expose it to the bad extensions/shims.
+      if (!ui.isModularize) {
+        addBizarroMethods();
+        document.write('<script src="' + ui.buildPath + '"><\/script>');
+      }
+    </script>
+    <script>
+      // Store lodash to test for bad extensions/shims.
+      if (!ui.isModularize) {
+        var lodashBizarro = window._;
+        window._ = undefined;
+        removeBizarroMethods();
+      }
+      // Load test scripts.
+      document.write((ui.isForeign || ui.urlParams.loader == 'none')
+        ? '<script src="' + ui.buildPath + '"><\/script><script src="test.js"><\/script>'
+        : '<script data-dojo-config="async:1" src="' + ui.loaderPath + '"><\/script>'
+      );
+    </script>
+    <script>
+      var lodashModule,
+          shimmedModule,
+          underscoreModule;
+
+      (function() {
+        if (window.curl) {
+          curl.config({ 'apiName': 'require' });
+        }
+        if (ui.isForeign || !window.require) {
+          return;
+        }
+        var reBasename = /[\w.-]+$/,
+            basePath = ('//' + location.host + location.pathname.replace(reBasename, '')).replace(/\btest\/$/, ''),
+            modulePath = ui.buildPath.replace(/\.js$/, ''),
+            moduleMain = modulePath.match(reBasename)[0],
+            locationPath = modulePath.replace(reBasename, '').replace(/^\/|\/$/g, ''),
+            shimmedLocationPath = './abc/../' + locationPath,
+            underscoreLocationPath = './xyz/../' + locationPath,
+            uid = +new Date;
+
+        function getConfig() {
+          var result = {
+            'baseUrl': './',
+            'urlArgs': 't=' + uid++,
+            'waitSeconds': 0,
+            'paths': {},
+            'packages': [{
+              'name': 'test',
+              'location': basePath + 'test',
+              'main': 'test',
+              'config': {
+                // Work around no global being exported.
+                'exports': 'QUnit',
+                'loader': 'curl/loader/legacy'
+              }
+            }],
+            'shim': {
+              'shimmed': {
+                'exports': '_'
+              }
+            }
+          };
+
+          if (ui.isModularize) {
+            result.packages.push({
+              'name': 'lodash',
+              'location': locationPath,
+              'main': moduleMain
+            }, {
+              'name': 'shimmed',
+              'location': shimmedLocationPath,
+              'main': moduleMain
+            }, {
+              'name': 'underscore',
+              'location': underscoreLocationPath,
+              'main': moduleMain
+            });
+          } else {
+            result.paths.lodash = modulePath;
+            result.paths.shimmed = shimmedLocationPath + '/' + moduleMain;
+            result.paths.underscore = underscoreLocationPath + '/' + moduleMain;
+          }
+          return result;
+        }
+
+        function loadTests() {
+          require(getConfig(), ['test'], function() {
+            QUnit.start();
+          });
+        }
+
+        function loadModulesAndTests() {
+          require(getConfig(), ['lodash', 'shimmed', 'underscore'], function(lodash, shimmed, underscore) {
+            lodashModule = lodash;
+            lodashModule.moduleName = 'lodash';
+
+            if (shimmed) {
+              shimmedModule = shimmed.result(shimmed, 'noConflict') || shimmed;
+              shimmedModule.moduleName = 'shimmed';
+            }
+            if (underscore) {
+              underscoreModule = underscore.result(underscore, 'noConflict') || underscore;
+              underscoreModule.moduleName = 'underscore';
+            }
+            window._ = lodash;
+
+            if (ui.isModularize) {
+              require(getConfig(), [
+                'lodash/_baseEach',
+                'lodash/_isIndex',
+                'lodash/_isIterateeCall'
+              ], function(baseEach, isIndex, isIterateeCall) {
+                lodash._baseEach = baseEach;
+                lodash._isIndex = isIndex;
+                lodash._isIterateeCall = isIterateeCall;
+                loadTests();
+              });
+            } else {
+              loadTests();
+            }
+          });
+        }
+
+        QUnit.config.autostart = false;
+
+        if (window.requirejs) {
+          addBizarroMethods();
+          require(getConfig(), ['lodash'], function(lodash) {
+            lodashBizarro = lodash.result(lodash, 'noConflict') || lodash;
+            delete requirejs.s.contexts._;
+
+            removeBizarroMethods();
+            loadModulesAndTests();
+          });
+        } else {
+          loadModulesAndTests();
+        }
+      }());
+
+      // Set a more readable browser name.
+      window.onload = function() {
+        var timeoutId = setInterval(function() {
+          var ua = document.getElementById('qunit-userAgent');
+          if (ua) {
+            ua.innerHTML = platform;
+            clearInterval(timeoutId);
+          }
+        }, 16);
+      };
+    </script>
+  </body>
+</html>
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/remove.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/remove.js
new file mode 100644
index 0000000..bd5aaf9
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/remove.js
@@ -0,0 +1,27 @@
+#!/usr/bin/env node
+'use strict';
+
+var _ = require('../lodash'),
+    fs = require('fs'),
+    path = require('path');
+
+var args = (args = process.argv)
+  .slice((args[0] === process.execPath || args[0] === 'node') ? 2 : 0);
+
+var filePath = path.resolve(args[1]),
+    reLine = /.*/gm;
+
+var pattern = (function() {
+  var result = args[0],
+      delimiter = result.charAt(0),
+      lastIndex = result.lastIndexOf(delimiter);
+
+  return RegExp(result.slice(1, lastIndex), result.slice(lastIndex + 1));
+}());
+
+/*----------------------------------------------------------------------------*/
+
+fs.writeFileSync(filePath, fs.readFileSync(filePath, 'utf8').replace(pattern, function(match) {
+  var snippet = _.slice(arguments, -3, -2)[0];
+  return match.replace(snippet, snippet.replace(reLine, ''));
+}));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/saucelabs.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/saucelabs.js
new file mode 100644
index 0000000..d10a313
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/saucelabs.js
@@ -0,0 +1,914 @@
+#!/usr/bin/env node
+'use strict';
+
+/** Environment shortcut. */
+var env = process.env;
+
+if (env.TRAVIS_SECURE_ENV_VARS == 'false') {
+  console.log('Skipping Sauce Labs jobs; secure environment variables are unavailable');
+  process.exit(0);
+}
+
+/** Load Node.js modules. */
+var EventEmitter = require('events').EventEmitter,
+    http = require('http'),
+    path = require('path'),
+    url = require('url'),
+    util = require('util');
+
+/** Load other modules. */
+var _ = require('../lodash.js'),
+    chalk = require('chalk'),
+    ecstatic = require('ecstatic'),
+    request = require('request'),
+    SauceTunnel = require('sauce-tunnel');
+
+/** Used for Sauce Labs credentials. */
+var accessKey = env.SAUCE_ACCESS_KEY,
+    username = env.SAUCE_USERNAME;
+
+/** Used as the default maximum number of times to retry a job and tunnel. */
+var maxJobRetries = 3,
+    maxTunnelRetries = 3;
+
+/** Used as the static file server middleware. */
+var mount = ecstatic({
+  'cache': 'no-cache',
+  'root': process.cwd()
+});
+
+/** Used as the list of ports supported by Sauce Connect. */
+var ports = [
+  80, 443, 888, 2000, 2001, 2020, 2109, 2222, 2310, 3000, 3001, 3030, 3210,
+  3333, 4000, 4001, 4040, 4321, 4502, 4503, 4567, 5000, 5001, 5050, 5555, 5432,
+  6000, 6001, 6060, 6666, 6543, 7000, 7070, 7774, 7777, 8000, 8001, 8003, 8031,
+  8080, 8081, 8765, 8777, 8888, 9000, 9001, 9080, 9090, 9876, 9877, 9999, 49221,
+  55001
+];
+
+/** Used by `logInline` to clear previously logged messages. */
+var prevLine = '';
+
+/** Method shortcut. */
+var push = Array.prototype.push;
+
+/** Used to detect error messages. */
+var reError = /(?:\be|E)rror\b/;
+
+/** Used to detect valid job ids. */
+var reJobId = /^[a-z0-9]{32}$/;
+
+/** Used to display the wait throbber. */
+var throbberDelay = 500,
+    waitCount = -1;
+
+/**
+ * Used as Sauce Labs config values.
+ * See the [Sauce Labs documentation](https://docs.saucelabs.com/reference/test-configuration/)
+ * for more details.
+ */
+var advisor = getOption('advisor', false),
+    build = getOption('build', (env.TRAVIS_COMMIT || '').slice(0, 10)),
+    commandTimeout = getOption('commandTimeout', 90),
+    compatMode = getOption('compatMode', null),
+    customData = Function('return {' + getOption('customData', '').replace(/^\{|}$/g, '') + '}')(),
+    deviceOrientation = getOption('deviceOrientation', 'portrait'),
+    framework = getOption('framework', 'qunit'),
+    idleTimeout = getOption('idleTimeout', 60),
+    jobName = getOption('name', 'unit tests'),
+    maxDuration = getOption('maxDuration', 180),
+    port = ports[Math.min(_.sortedIndex(ports, getOption('port', 9001)), ports.length - 1)],
+    publicAccess = getOption('public', true),
+    queueTimeout = getOption('queueTimeout', 240),
+    recordVideo = getOption('recordVideo', true),
+    recordScreenshots = getOption('recordScreenshots', false),
+    runner = getOption('runner', 'test/index.html').replace(/^\W+/, ''),
+    runnerUrl = getOption('runnerUrl', 'http://localhost:' + port + '/' + runner),
+    statusInterval = getOption('statusInterval', 5),
+    tags = getOption('tags', []),
+    throttled = getOption('throttled', 10),
+    tunneled = getOption('tunneled', true),
+    tunnelId = getOption('tunnelId', 'tunnel_' + (env.TRAVIS_JOB_ID || 0)),
+    tunnelTimeout = getOption('tunnelTimeout', 120),
+    videoUploadOnPass = getOption('videoUploadOnPass', false);
+
+/** Used to convert Sauce Labs browser identifiers to their formal names. */
+var browserNameMap = {
+  'googlechrome': 'Chrome',
+  'iehta': 'Internet Explorer',
+  'ipad': 'iPad',
+  'iphone': 'iPhone',
+  'microsoftedge': 'Edge'
+};
+
+/** List of platforms to load the runner on. */
+var platforms = [
+  ['Linux', 'android', '5.1'],
+  ['Windows 10', 'chrome', '49'],
+  ['Windows 10', 'chrome', '48'],
+  ['Windows 10', 'firefox', '45'],
+  ['Windows 10', 'firefox', '44'],
+  ['Windows 10', 'microsoftedge', '13'],
+  ['Windows 10', 'internet explorer', '11'],
+  ['Windows 8', 'internet explorer', '10'],
+  ['Windows 7', 'internet explorer', '9'],
+  // ['OS X 10.10', 'ipad', '9.1'],
+  ['OS X 10.11', 'safari', '9'],
+  ['OS X 10.10', 'safari', '8']
+];
+
+/** Used to tailor the `platforms` array. */
+var isAMD = _.includes(tags, 'amd'),
+    isBackbone = _.includes(tags, 'backbone'),
+    isModern = _.includes(tags, 'modern');
+
+// The platforms to test IE compatibility modes.
+if (compatMode) {
+  platforms = [
+    ['Windows 10', 'internet explorer', '11'],
+    ['Windows 8', 'internet explorer', '10'],
+    ['Windows 7', 'internet explorer', '9'],
+    ['Windows 7', 'internet explorer', '8']
+  ];
+}
+// The platforms for AMD tests.
+if (isAMD) {
+  platforms = _.filter(platforms, function(platform) {
+    var browser = browserName(platform[1]),
+        version = +platform[2];
+
+    switch (browser) {
+      case 'Android': return version >= 4.4;
+      case 'Opera': return version >= 10;
+    }
+    return true;
+  });
+}
+// The platforms for Backbone tests.
+if (isBackbone) {
+  platforms = _.filter(platforms, function(platform) {
+    var browser = browserName(platform[1]),
+        version = +platform[2];
+
+    switch (browser) {
+      case 'Firefox': return version >= 4;
+      case 'Internet Explorer': return version >= 7;
+      case 'iPad': return version >= 5;
+      case 'Opera': return version >= 12;
+    }
+    return true;
+  });
+}
+// The platforms for modern builds.
+if (isModern) {
+  platforms = _.filter(platforms, function(platform) {
+    var browser = browserName(platform[1]),
+        version = +platform[2];
+
+    switch (browser) {
+      case 'Android': return version >= 4.1;
+      case 'Firefox': return version >= 10;
+      case 'Internet Explorer': return version >= 9;
+      case 'iPad': return version >= 6;
+      case 'Opera': return version >= 12;
+      case 'Safari': return version >= 6;
+    }
+    return true;
+  });
+}
+
+/** Used as the default `Job` options object. */
+var jobOptions = {
+  'build': build,
+  'command-timeout': commandTimeout,
+  'custom-data': customData,
+  'device-orientation': deviceOrientation,
+  'framework': framework,
+  'idle-timeout': idleTimeout,
+  'max-duration': maxDuration,
+  'name': jobName,
+  'public': publicAccess,
+  'platforms': platforms,
+  'record-screenshots': recordScreenshots,
+  'record-video': recordVideo,
+  'sauce-advisor': advisor,
+  'tags': tags,
+  'url': runnerUrl,
+  'video-upload-on-pass': videoUploadOnPass
+};
+
+if (publicAccess === true) {
+  jobOptions['public'] = 'public';
+}
+if (tunneled) {
+  jobOptions['tunnel-identifier'] = tunnelId;
+}
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * Resolves the formal browser name for a given Sauce Labs browser identifier.
+ *
+ * @private
+ * @param {string} identifier The browser identifier.
+ * @returns {string} Returns the formal browser name.
+ */
+function browserName(identifier) {
+  return browserNameMap[identifier] || _.startCase(identifier);
+}
+
+/**
+ * Gets the value for the given option name. If no value is available the
+ * `defaultValue` is returned.
+ *
+ * @private
+ * @param {string} name The name of the option.
+ * @param {*} defaultValue The default option value.
+ * @returns {*} Returns the option value.
+ */
+function getOption(name, defaultValue) {
+  var isArr = _.isArray(defaultValue);
+  return _.reduce(process.argv, function(result, value) {
+    if (isArr) {
+      value = optionToArray(name, value);
+      return _.isEmpty(value) ? result : value;
+    }
+    value = optionToValue(name, value);
+
+    return value == null ? result : value;
+  }, defaultValue);
+}
+
+/**
+ * Checks if `value` is a job ID.
+ *
+ * @private
+ * @param {*} value The value to check.
+ * @returns {boolean} Returns `true` if `value` is a job ID, else `false`.
+ */
+function isJobId(value) {
+  return reJobId.test(value);
+}
+
+/**
+ * Writes an inline message to standard output.
+ *
+ * @private
+ * @param {string} [text=''] The text to log.
+ */
+function logInline(text) {
+  var blankLine = _.repeat(' ', _.size(prevLine));
+  prevLine = text = _.truncate(text, { 'length': 40 });
+  process.stdout.write(text + blankLine.slice(text.length) + '\r');
+}
+
+/**
+ * Writes the wait throbber to standard output.
+ *
+ * @private
+ */
+function logThrobber() {
+  logInline('Please wait' + _.repeat('.', (++waitCount % 3) + 1));
+}
+
+/**
+ * Converts a comma separated option value into an array.
+ *
+ * @private
+ * @param {string} name The name of the option to inspect.
+ * @param {string} string The options string.
+ * @returns {Array} Returns the new converted array.
+ */
+function optionToArray(name, string) {
+  return _.compact(_.invokeMap((optionToValue(name, string) || '').split(/, */), 'trim'));
+}
+
+/**
+ * Extracts the option value from an option string.
+ *
+ * @private
+ * @param {string} name The name of the option to inspect.
+ * @param {string} string The options string.
+ * @returns {string|undefined} Returns the option value, else `undefined`.
+ */
+function optionToValue(name, string) {
+  var result = string.match(RegExp('^' + name + '(?:=([\\s\\S]+))?$'));
+  if (result) {
+    result = _.result(result, 1);
+    result = result ? _.trim(result) : true;
+  }
+  if (result === 'false') {
+    return false;
+  }
+  return result || undefined;
+}
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * The `Job#remove` and `Tunnel#stop` callback used by `Jobs#restart`
+ * and `Tunnel#restart` respectively.
+ *
+ * @private
+ */
+function onGenericRestart() {
+  this.restarting = false;
+  this.emit('restart');
+  this.start();
+}
+
+/**
+ * The `request.put` and `SauceTunnel#stop` callback used by `Jobs#stop`
+ * and `Tunnel#stop` respectively.
+ *
+ * @private
+ * @param {Object} [error] The error object.
+ */
+function onGenericStop(error) {
+  this.running = this.stopping = false;
+  this.emit('stop', error);
+}
+
+/**
+ * The `request.del` callback used by `Jobs#remove`.
+ *
+ * @private
+ */
+function onJobRemove(error, res, body) {
+  this.id = this.taskId = this.url = null;
+  this.removing = false;
+  this.emit('remove');
+}
+
+/**
+ * The `Job#remove` callback used by `Jobs#reset`.
+ *
+ * @private
+ */
+function onJobReset() {
+  this.attempts = 0;
+  this.failed = this.resetting = false;
+  this._pollerId = this.id = this.result = this.taskId = this.url = null;
+  this.emit('reset');
+}
+
+/**
+ * The `request.post` callback used by `Jobs#start`.
+ *
+ * @private
+ * @param {Object} [error] The error object.
+ * @param {Object} res The response data object.
+ * @param {Object} body The response body JSON object.
+ */
+function onJobStart(error, res, body) {
+  this.starting = false;
+
+  if (this.stopping) {
+    return;
+  }
+  var statusCode = _.result(res, 'statusCode'),
+      taskId = _.first(_.result(body, 'js tests'));
+
+  if (error || !taskId || statusCode != 200) {
+    if (this.attempts < this.retries) {
+      this.restart();
+      return;
+    }
+    var na = 'unavailable',
+        bodyStr = _.isObject(body) ? '\n' + JSON.stringify(body) : na,
+        statusStr = _.isFinite(statusCode) ? statusCode : na;
+
+    logInline();
+    console.error('Failed to start job; status: %s, body: %s', statusStr, bodyStr);
+    if (error) {
+      console.error(error);
+    }
+    this.failed = true;
+    this.emit('complete');
+    return;
+  }
+  this.running = true;
+  this.taskId = taskId;
+  this.timestamp = _.now();
+  this.emit('start');
+  this.status();
+}
+
+/**
+ * The `request.post` callback used by `Job#status`.
+ *
+ * @private
+ * @param {Object} [error] The error object.
+ * @param {Object} res The response data object.
+ * @param {Object} body The response body JSON object.
+ */
+function onJobStatus(error, res, body) {
+  this.checking = false;
+
+  if (!this.running || this.stopping) {
+    return;
+  }
+  var completed = _.result(body, 'completed', false),
+      data = _.first(_.result(body, 'js tests')),
+      elapsed = (_.now() - this.timestamp) / 1000,
+      jobId = _.result(data, 'job_id', null),
+      jobResult = _.result(data, 'result', null),
+      jobStatus = _.result(data, 'status', ''),
+      jobUrl = _.result(data, 'url', null),
+      expired = (elapsed >= queueTimeout && !_.includes(jobStatus, 'in progress')),
+      options = this.options,
+      platform = options.platforms[0];
+
+  if (_.isObject(jobResult)) {
+    var message = _.result(jobResult, 'message');
+  } else {
+    if (typeof jobResult == 'string') {
+      message = jobResult;
+    }
+    jobResult = null;
+  }
+  if (isJobId(jobId)) {
+    this.id = jobId;
+    this.result = jobResult;
+    this.url = jobUrl;
+  } else {
+    completed = false;
+  }
+  this.emit('status', jobStatus);
+
+  if (!completed && !expired) {
+    this._pollerId = _.delay(_.bind(this.status, this), this.statusInterval * 1000);
+    return;
+  }
+  var description = browserName(platform[1]) + ' ' + platform[2] + ' on ' + _.startCase(platform[0]),
+      errored = !jobResult || !jobResult.passed || reError.test(message) || reError.test(jobStatus),
+      failures = _.result(jobResult, 'failed'),
+      label = options.name + ':',
+      tunnel = this.tunnel;
+
+  if (errored || failures) {
+    if (errored && this.attempts < this.retries) {
+      this.restart();
+      return;
+    }
+    var details = 'See ' + jobUrl + ' for details.';
+    this.failed = true;
+
+    logInline();
+    if (failures) {
+      console.error(label + ' %s ' + chalk.red('failed') + ' %d test' + (failures > 1 ? 's' : '') + '. %s', description, failures, details);
+    }
+    else if (tunnel.attempts < tunnel.retries) {
+      tunnel.restart();
+      return;
+    }
+    else {
+      if (typeof message == 'undefined') {
+        message = 'Results are unavailable. ' + details;
+      }
+      console.error(label, description, chalk.red('failed') + ';', message);
+    }
+  }
+  else {
+    logInline();
+    console.log(label, description, chalk.green('passed'));
+  }
+  this.running = false;
+  this.emit('complete');
+}
+
+/**
+ * The `SauceTunnel#start` callback used by `Tunnel#start`.
+ *
+ * @private
+ * @param {boolean} success The connection success indicator.
+ */
+function onTunnelStart(success) {
+  this.starting = false;
+
+  if (this._timeoutId) {
+    clearTimeout(this._timeoutId);
+    this._timeoutId = null;
+  }
+  if (!success) {
+    if (this.attempts < this.retries) {
+      this.restart();
+      return;
+    }
+    logInline();
+    console.error('Failed to open Sauce Connect tunnel');
+    process.exit(2);
+  }
+  logInline();
+  console.log('Sauce Connect tunnel opened');
+
+  var jobs = this.jobs;
+  push.apply(jobs.queue, jobs.all);
+
+  this.running = true;
+  this.emit('start');
+
+  console.log('Starting jobs...');
+  this.dequeue();
+}
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * The Job constructor.
+ *
+ * @private
+ * @param {Object} [properties] The properties to initialize a job with.
+ */
+function Job(properties) {
+  EventEmitter.call(this);
+
+  this.options = {};
+  _.merge(this, properties);
+  _.defaults(this.options, _.cloneDeep(jobOptions));
+
+  this.attempts = 0;
+  this.checking = this.failed = this.removing = this.resetting = this.restarting = this.running = this.starting = this.stopping = false;
+  this._pollerId = this.id = this.result = this.taskId = this.url = null;
+}
+
+util.inherits(Job, EventEmitter);
+
+/**
+ * Removes the job.
+ *
+ * @memberOf Job
+ * @param {Function} callback The function called once the job is removed.
+ * @param {Object} Returns the job instance.
+ */
+Job.prototype.remove = function(callback) {
+  this.once('remove', _.iteratee(callback));
+  if (this.removing) {
+    return this;
+  }
+  this.removing = true;
+  return this.stop(function() {
+    var onRemove = _.bind(onJobRemove, this);
+    if (!this.id) {
+      _.defer(onRemove);
+      return;
+    }
+    request.del(_.template('https://saucelabs.com/rest/v1/${user}/jobs/${id}')(this), {
+      'auth': { 'user': this.user, 'pass': this.pass }
+    }, onRemove);
+  });
+};
+
+/**
+ * Resets the job.
+ *
+ * @memberOf Job
+ * @param {Function} callback The function called once the job is reset.
+ * @param {Object} Returns the job instance.
+ */
+Job.prototype.reset = function(callback) {
+  this.once('reset', _.iteratee(callback));
+  if (this.resetting) {
+    return this;
+  }
+  this.resetting = true;
+  return this.remove(onJobReset);
+};
+
+/**
+ * Restarts the job.
+ *
+ * @memberOf Job
+ * @param {Function} callback The function called once the job is restarted.
+ * @param {Object} Returns the job instance.
+ */
+Job.prototype.restart = function(callback) {
+  this.once('restart', _.iteratee(callback));
+  if (this.restarting) {
+    return this;
+  }
+  this.restarting = true;
+
+  var options = this.options,
+      platform = options.platforms[0],
+      description = browserName(platform[1]) + ' ' + platform[2] + ' on ' + _.startCase(platform[0]),
+      label = options.name + ':';
+
+  logInline();
+  console.log('%s %s restart %d of %d', label, description, ++this.attempts, this.retries);
+
+  return this.remove(onGenericRestart);
+};
+
+/**
+ * Starts the job.
+ *
+ * @memberOf Job
+ * @param {Function} callback The function called once the job is started.
+ * @param {Object} Returns the job instance.
+ */
+Job.prototype.start = function(callback) {
+  this.once('start', _.iteratee(callback));
+  if (this.starting || this.running) {
+    return this;
+  }
+  this.starting = true;
+  request.post(_.template('https://saucelabs.com/rest/v1/${user}/js-tests')(this), {
+    'auth': { 'user': this.user, 'pass': this.pass },
+    'json': this.options
+  }, _.bind(onJobStart, this));
+
+  return this;
+};
+
+/**
+ * Checks the status of a job.
+ *
+ * @memberOf Job
+ * @param {Function} callback The function called once the status is resolved.
+ * @param {Object} Returns the job instance.
+ */
+Job.prototype.status = function(callback) {
+  this.once('status', _.iteratee(callback));
+  if (this.checking || this.removing || this.resetting || this.restarting || this.starting || this.stopping) {
+    return this;
+  }
+  this._pollerId = null;
+  this.checking = true;
+  request.post(_.template('https://saucelabs.com/rest/v1/${user}/js-tests/status')(this), {
+    'auth': { 'user': this.user, 'pass': this.pass },
+    'json': { 'js tests': [this.taskId] }
+  }, _.bind(onJobStatus, this));
+
+  return this;
+};
+
+/**
+ * Stops the job.
+ *
+ * @memberOf Job
+ * @param {Function} callback The function called once the job is stopped.
+ * @param {Object} Returns the job instance.
+ */
+Job.prototype.stop = function(callback) {
+  this.once('stop', _.iteratee(callback));
+  if (this.stopping) {
+    return this;
+  }
+  this.stopping = true;
+  if (this._pollerId) {
+    clearTimeout(this._pollerId);
+    this._pollerId = null;
+    this.checking = false;
+  }
+  var onStop = _.bind(onGenericStop, this);
+  if (!this.running || !this.id) {
+    _.defer(onStop);
+    return this;
+  }
+  request.put(_.template('https://saucelabs.com/rest/v1/${user}/jobs/${id}/stop')(this), {
+    'auth': { 'user': this.user, 'pass': this.pass }
+  }, onStop);
+
+  return this;
+};
+
+/*----------------------------------------------------------------------------*/
+
+/**
+ * The Tunnel constructor.
+ *
+ * @private
+ * @param {Object} [properties] The properties to initialize the tunnel with.
+ */
+function Tunnel(properties) {
+  EventEmitter.call(this);
+
+  _.merge(this, properties);
+
+  var active = [],
+      queue = [];
+
+  var all = _.map(this.platforms, _.bind(function(platform) {
+    return new Job(_.merge({
+      'user': this.user,
+      'pass': this.pass,
+      'tunnel': this,
+      'options': { 'platforms': [platform] }
+    }, this.job));
+  }, this));
+
+  var completed = 0,
+      restarted = [],
+      success = true,
+      total = all.length,
+      tunnel = this;
+
+  _.invokeMap(all, 'on', 'complete', function() {
+    _.pull(active, this);
+    if (success) {
+      success = !this.failed;
+    }
+    if (++completed == total) {
+      tunnel.stop(_.partial(tunnel.emit, 'complete', success));
+      return;
+    }
+    tunnel.dequeue();
+  });
+
+  _.invokeMap(all, 'on', 'restart', function() {
+    if (!_.includes(restarted, this)) {
+      restarted.push(this);
+    }
+    // Restart tunnel if all active jobs have restarted.
+    var threshold = Math.min(all.length, _.isFinite(throttled) ? throttled : 3);
+    if (tunnel.attempts < tunnel.retries &&
+        active.length >= threshold && _.isEmpty(_.difference(active, restarted))) {
+      tunnel.restart();
+    }
+  });
+
+  this.on('restart', function() {
+    completed = 0;
+    success = true;
+    restarted.length = 0;
+  });
+
+  this._timeoutId = null;
+  this.attempts = 0;
+  this.restarting = this.running = this.starting = this.stopping = false;
+  this.jobs = { 'active': active, 'all': all, 'queue': queue };
+  this.connection = new SauceTunnel(this.user, this.pass, this.id, this.tunneled, ['-P', '0']);
+}
+
+util.inherits(Tunnel, EventEmitter);
+
+/**
+ * Restarts the tunnel.
+ *
+ * @memberOf Tunnel
+ * @param {Function} callback The function called once the tunnel is restarted.
+ */
+Tunnel.prototype.restart = function(callback) {
+  this.once('restart', _.iteratee(callback));
+  if (this.restarting) {
+    return this;
+  }
+  this.restarting = true;
+
+  logInline();
+  console.log('Tunnel %s: restart %d of %d', this.id, ++this.attempts, this.retries);
+
+  var jobs = this.jobs,
+      active = jobs.active,
+      all = jobs.all;
+
+  var reset = _.after(all.length, _.bind(this.stop, this, onGenericRestart)),
+      stop = _.after(active.length, _.partial(_.invokeMap, all, 'reset', reset));
+
+  if (_.isEmpty(active)) {
+    _.defer(stop);
+  }
+  if (_.isEmpty(all)) {
+    _.defer(reset);
+  }
+  _.invokeMap(active, 'stop', function() {
+    _.pull(active, this);
+    stop();
+  });
+
+  if (this._timeoutId) {
+    clearTimeout(this._timeoutId);
+    this._timeoutId = null;
+  }
+  return this;
+};
+
+/**
+ * Starts the tunnel.
+ *
+ * @memberOf Tunnel
+ * @param {Function} callback The function called once the tunnel is started.
+ * @param {Object} Returns the tunnel instance.
+ */
+Tunnel.prototype.start = function(callback) {
+  this.once('start', _.iteratee(callback));
+  if (this.starting || this.running) {
+    return this;
+  }
+  this.starting = true;
+
+  logInline();
+  console.log('Opening Sauce Connect tunnel...');
+
+  var onStart = _.bind(onTunnelStart, this);
+  if (this.timeout) {
+    this._timeoutId = _.delay(onStart, this.timeout * 1000, false);
+  }
+  this.connection.start(onStart);
+  return this;
+};
+
+/**
+ * Removes jobs from the queue and starts them.
+ *
+ * @memberOf Tunnel
+ * @param {Object} Returns the tunnel instance.
+ */
+Tunnel.prototype.dequeue = function() {
+  var count = 0,
+      jobs = this.jobs,
+      active = jobs.active,
+      queue = jobs.queue,
+      throttled = this.throttled;
+
+  while (queue.length && (active.length < throttled)) {
+    var job = queue.shift();
+    active.push(job);
+    _.delay(_.bind(job.start, job), ++count * 1000);
+  }
+  return this;
+};
+
+/**
+ * Stops the tunnel.
+ *
+ * @memberOf Tunnel
+ * @param {Function} callback The function called once the tunnel is stopped.
+ * @param {Object} Returns the tunnel instance.
+ */
+Tunnel.prototype.stop = function(callback) {
+  this.once('stop', _.iteratee(callback));
+  if (this.stopping) {
+    return this;
+  }
+  this.stopping = true;
+
+  logInline();
+  console.log('Shutting down Sauce Connect tunnel...');
+
+  var jobs = this.jobs,
+      active = jobs.active;
+
+  var stop = _.after(active.length, _.bind(function() {
+    var onStop = _.bind(onGenericStop, this);
+    if (this.running) {
+      this.connection.stop(onStop);
+    } else {
+      onStop();
+    }
+  }, this));
+
+  jobs.queue.length = 0;
+  if (_.isEmpty(active)) {
+    _.defer(stop);
+  }
+  _.invokeMap(active, 'stop', function() {
+    _.pull(active, this);
+    stop();
+  });
+
+  if (this._timeoutId) {
+    clearTimeout(this._timeoutId);
+    this._timeoutId = null;
+  }
+  return this;
+};
+
+/*----------------------------------------------------------------------------*/
+
+// Cleanup any inline logs when exited via `ctrl+c`.
+process.on('SIGINT', function() {
+  logInline();
+  process.exit();
+});
+
+// Create a web server for the current working directory.
+http.createServer(function(req, res) {
+  // See http://msdn.microsoft.com/en-us/library/ff955275(v=vs.85).aspx.
+  if (compatMode && path.extname(url.parse(req.url).pathname) == '.html') {
+    res.setHeader('X-UA-Compatible', 'IE=' + compatMode);
+  }
+  mount(req, res);
+}).listen(port);
+
+// Setup Sauce Connect so we can use this server from Sauce Labs.
+var tunnel = new Tunnel({
+  'user': username,
+  'pass': accessKey,
+  'id': tunnelId,
+  'job': { 'retries': maxJobRetries, 'statusInterval': statusInterval },
+  'platforms': platforms,
+  'retries': maxTunnelRetries,
+  'throttled': throttled,
+  'tunneled': tunneled,
+  'timeout': tunnelTimeout
+});
+
+tunnel.on('complete', function(success) {
+  process.exit(success ? 0 : 1);
+});
+
+tunnel.start();
+
+setInterval(logThrobber, throbberDelay);
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/test-fp.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/test-fp.js
new file mode 100644
index 0000000..775d7ee
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/test-fp.js
@@ -0,0 +1,1769 @@
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used as the size to cover large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Used as a reference to the global object. */
+  var root = (typeof global == 'object' && global) || this;
+
+  /** Used for native method references. */
+  var arrayProto = Array.prototype;
+
+  /** Method and object shortcuts. */
+  var phantom = root.phantom,
+      amd = root.define && define.amd,
+      argv = root.process && process.argv,
+      document = !phantom && root.document,
+      noop = function() {},
+      slice = arrayProto.slice,
+      WeakMap = root.WeakMap;
+
+  // Leak to avoid sporadic `noglobals` fails on Edge in Sauce Labs.
+  root.msWDfn = undefined;
+
+  /*--------------------------------------------------------------------------*/
+
+  /** Use a single "load" function. */
+  var load = (!amd && typeof require == 'function')
+    ? require
+    : noop;
+
+  /** The unit testing framework. */
+  var QUnit = root.QUnit || (root.QUnit = (
+    QUnit = load('../node_modules/qunitjs/qunit/qunit.js') || root.QUnit,
+    QUnit = QUnit.QUnit || QUnit
+  ));
+
+  /** Load stable Lodash and QUnit Extras. */
+  var _ = root._ || (root._ = (
+    _ = load('../lodash.js'),
+    _.runInContext(root)
+  ));
+
+  var QUnitExtras = load('../node_modules/qunit-extras/qunit-extras.js');
+  if (QUnitExtras) {
+    QUnitExtras.runInContext(root);
+  }
+
+  var convert = (function() {
+    var baseConvert = root.fp || load('../fp/_baseConvert.js');
+    if (!root.fp) {
+      return function(name, func, options) {
+        return baseConvert(_, name, func, options);
+      };
+    }
+    return function(name, func, options) {
+      if (typeof name == 'function') {
+        options = func;
+        func = name;
+        name = undefined;
+      }
+      return name === undefined
+        ? baseConvert(func, options)
+        : baseConvert(_.runInContext(), options)[name];
+    };
+  }());
+
+  var allFalseOptions = {
+    'cap': false,
+    'curry': false,
+    'fixed': false,
+    'immutable': false,
+    'rearg': false
+  };
+
+  var fp = root.fp
+    ? (fp = _.noConflict(), _ = root._, fp)
+    : convert(_.runInContext());
+
+  var mapping = root.mapping || load('../fp/_mapping.js');
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Skips a given number of tests with a passing result.
+   *
+   * @private
+   * @param {Object} assert The QUnit assert object.
+   * @param {number} [count=1] The number of tests to skip.
+   */
+  function skipAssert(assert, count) {
+    count || (count = 1);
+    while (count--) {
+      assert.ok(true, 'test skipped');
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  if (argv) {
+    console.log('Running lodash/fp tests.');
+  }
+
+  QUnit.module('convert module');
+
+  (function() {
+    QUnit.test('should work with `name` and `func`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4],
+          remove = convert('remove', _.remove);
+
+      var actual = remove(function(n) {
+        return n % 2 == 0;
+      })(array);
+
+      assert.deepEqual(array, [1, 2, 3, 4]);
+      assert.deepEqual(actual, [1, 3]);
+    });
+
+    QUnit.test('should work with `name`, `func`, and `options`', function(assert) {
+      assert.expect(3);
+
+      var array = [1, 2, 3, 4],
+          remove = convert('remove', _.remove, allFalseOptions);
+
+      var actual = remove(array, function(n, index) {
+        return index % 2 == 0;
+      });
+
+      assert.deepEqual(array, [2, 4]);
+      assert.deepEqual(actual, [1, 3]);
+      assert.deepEqual(remove(), []);
+    });
+
+    QUnit.test('should work with an object', function(assert) {
+      assert.expect(2);
+
+      if (!document) {
+        var array = [1, 2, 3, 4],
+            lodash = convert({ 'remove': _.remove });
+
+        var actual = lodash.remove(function(n) {
+          return n % 2 == 0;
+        })(array);
+
+        assert.deepEqual(array, [1, 2, 3, 4]);
+        assert.deepEqual(actual, [1, 3]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should work with an object and `options`', function(assert) {
+      assert.expect(3);
+
+      if (!document) {
+        var array = [1, 2, 3, 4],
+            lodash = convert({ 'remove': _.remove }, allFalseOptions);
+
+        var actual = lodash.remove(array, function(n, index) {
+          return index % 2 == 0;
+        });
+
+        assert.deepEqual(array, [2, 4]);
+        assert.deepEqual(actual, [1, 3]);
+        assert.deepEqual(lodash.remove(), []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should work with lodash and `options`', function(assert) {
+      assert.expect(3);
+
+      var array = [1, 2, 3, 4],
+          lodash = convert(_.runInContext(), allFalseOptions);
+
+      var actual = lodash.remove(array, function(n, index) {
+        return index % 2 == 0;
+      });
+
+      assert.deepEqual(array, [2, 4]);
+      assert.deepEqual(actual, [1, 3]);
+      assert.deepEqual(lodash.remove(), []);
+    });
+
+    QUnit.test('should work with `runInContext` and `options`', function(assert) {
+      assert.expect(3);
+
+      var array = [1, 2, 3, 4],
+          runInContext = convert('runInContext', _.runInContext, allFalseOptions),
+          lodash = runInContext();
+
+      var actual = lodash.remove(array, function(n, index) {
+        return index % 2 == 0;
+      });
+
+      assert.deepEqual(array, [2, 4]);
+      assert.deepEqual(actual, [1, 3]);
+      assert.deepEqual(lodash.remove(), []);
+    });
+
+    QUnit.test('should accept a variety of options', function(assert) {
+      assert.expect(8);
+
+      var array = [1, 2, 3, 4],
+          predicate = function(n) { return n % 2 == 0; },
+          value = _.clone(array),
+          remove = convert('remove', _.remove, { 'cap': false }),
+          actual = remove(function(n, index) { return index % 2 == 0; })(value);
+
+      assert.deepEqual(value, [1, 2, 3, 4]);
+      assert.deepEqual(actual, [2, 4]);
+
+      remove = convert('remove', _.remove, { 'curry': false });
+      actual = remove(predicate);
+
+      assert.deepEqual(actual, []);
+
+      var trim = convert('trim', _.trim, { 'fixed': false });
+      assert.strictEqual(trim('_-abc-_', '_-'), 'abc');
+
+      value = _.clone(array);
+      remove = convert('remove', _.remove, { 'immutable': false });
+      actual = remove(predicate)(value);
+
+      assert.deepEqual(value, [1, 3]);
+      assert.deepEqual(actual, [2, 4]);
+
+      value = _.clone(array);
+      remove = convert('remove', _.remove, { 'rearg': false });
+      actual = remove(value)(predicate);
+
+      assert.deepEqual(value, [1, 2, 3, 4]);
+      assert.deepEqual(actual, [1, 3]);
+    });
+
+    QUnit.test('should respect the `cap` option', function(assert) {
+      assert.expect(1);
+
+      var iteratee = convert('iteratee', _.iteratee, { 'cap': false });
+
+      var func = iteratee(function(a, b, c) {
+        return [a, b, c];
+      }, 3);
+
+      assert.deepEqual(func(1, 2, 3), [1, 2, 3]);
+    });
+
+    QUnit.test('should respect the `rearg` option', function(assert) {
+      assert.expect(1);
+
+      var add = convert('add', _.add, { 'rearg': true });
+
+      assert.strictEqual(add('2')('1'), '12');
+    });
+
+    QUnit.test('should only add a `placeholder` property if needed', function(assert) {
+      assert.expect(2);
+
+      if (!document) {
+        var methodNames = _.keys(mapping.placeholder),
+            expected = _.map(methodNames, _.constant(true));
+
+        var actual = _.map(methodNames, function(methodName) {
+          var object = {};
+          object[methodName] = _[methodName];
+
+          var lodash = convert(object);
+          return methodName in lodash;
+        });
+
+        assert.deepEqual(actual, expected);
+
+        var lodash = convert({ 'add': _.add });
+        assert.notOk('placeholder' in lodash);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('method.convert');
+
+  (function() {
+    QUnit.test('should exist on unconverted methods', function(assert) {
+      assert.expect(2);
+
+      var array = [],
+          isArray = fp.isArray.convert({ 'curry': true });
+
+      assert.strictEqual(fp.isArray(array), true);
+      assert.strictEqual(isArray()(array), true);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('convert methods');
+
+  _.each(['fp.convert', 'method.convert'], function(methodName) {
+    var isFp = methodName == 'fp.convert',
+        func = isFp ? fp.convert : fp.remove.convert;
+
+    QUnit.test('`' + methodName + '` should work with an object', function(assert) {
+      assert.expect(3);
+
+      var array = [1, 2, 3, 4],
+          lodash = func(allFalseOptions),
+          remove = isFp ? lodash.remove : lodash;
+
+      var actual = remove(array, function(n, index) {
+        return index % 2 == 0;
+      });
+
+      assert.deepEqual(array, [2, 4]);
+      assert.deepEqual(actual, [1, 3]);
+      assert.deepEqual(remove(), []);
+    });
+
+    QUnit.test('`' + methodName + '` should extend existing configs', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4],
+          lodash = func({ 'cap': false }),
+          remove = (isFp ? lodash.remove : lodash).convert({ 'rearg': false });
+
+      var actual = remove(array)(function(n, index) {
+        return index % 2 == 0;
+      });
+
+      assert.deepEqual(array, [1, 2, 3, 4]);
+      assert.deepEqual(actual, [2, 4]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('method arity checks');
+
+  (function() {
+    QUnit.test('should wrap methods with an arity > `1`', function(assert) {
+      assert.expect(1);
+
+      var methodNames = _.filter(_.functions(fp), function(methodName) {
+        return fp[methodName].length > 1;
+      });
+
+      assert.deepEqual(methodNames, []);
+    });
+
+    QUnit.test('should have >= arity of `aryMethod` designation', function(assert) {
+      assert.expect(4);
+
+      _.times(4, function(index) {
+        var aryCap = index + 1;
+
+        var methodNames = _.filter(mapping.aryMethod[aryCap], function(methodName) {
+          var key = _.result(mapping.remap, methodName, methodName),
+              arity = _[key].length;
+
+          return arity != 0 && arity < aryCap;
+        });
+
+        assert.deepEqual(methodNames, [], '`aryMethod[' + aryCap + ']`');
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('method aliases');
+
+  (function() {
+    QUnit.test('should have correct aliases', function(assert) {
+      assert.expect(1);
+
+      var actual = _.transform(mapping.aliasToReal, function(result, realName, alias) {
+        result.push([alias, fp[alias] === fp[realName]]);
+      }, []);
+
+      assert.deepEqual(_.reject(actual, 1), []);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('method ary caps');
+
+  (function() {
+    QUnit.test('should have a cap of 1', function(assert) {
+      assert.expect(1);
+
+      var funcMethods = [
+        'curry', 'iteratee', 'memoize', 'over', 'overEvery', 'overSome',
+        'method', 'methodOf', 'rest', 'runInContext'
+      ];
+
+      var exceptions = funcMethods.concat('mixin', 'template'),
+          expected = _.map(mapping.aryMethod[1], _.constant(true));
+
+      var actual = _.map(mapping.aryMethod[1], function(methodName) {
+        var arg = _.includes(funcMethods, methodName) ? _.noop : 1,
+            result = _.attempt(function() { return fp[methodName](arg); });
+
+        if (_.includes(exceptions, methodName)
+              ? typeof result == 'function'
+              : typeof result != 'function'
+            ) {
+          return true;
+        }
+        console.log(methodName, result);
+        return false;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should have a cap of 2', function(assert) {
+      assert.expect(1);
+
+      var funcMethods = [
+        'after', 'ary', 'before', 'bind', 'bindKey', 'curryN', 'debounce',
+        'delay', 'overArgs', 'partial', 'partialRight', 'rearg', 'throttle',
+        'wrap'
+      ];
+
+      var exceptions = _.difference(funcMethods.concat('matchesProperty'), ['cloneDeepWith', 'cloneWith', 'delay']),
+          expected = _.map(mapping.aryMethod[2], _.constant(true));
+
+      var actual = _.map(mapping.aryMethod[2], function(methodName) {
+        var args = _.includes(funcMethods, methodName) ? [methodName == 'curryN' ? 1 : _.noop, _.noop] : [1, []],
+            result = _.attempt(function() { return fp[methodName](args[0])(args[1]); });
+
+        if (_.includes(exceptions, methodName)
+              ? typeof result == 'function'
+              : typeof result != 'function'
+            ) {
+          return true;
+        }
+        console.log(methodName, result);
+        return false;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should have a cap of 3', function(assert) {
+      assert.expect(1);
+
+      var funcMethods = [
+        'assignWith', 'extendWith', 'isEqualWith', 'isMatchWith', 'reduce',
+        'reduceRight', 'transform', 'zipWith'
+      ];
+
+      var expected = _.map(mapping.aryMethod[3], _.constant(true));
+
+      var actual = _.map(mapping.aryMethod[3], function(methodName) {
+        var args = _.includes(funcMethods, methodName) ? [_.noop, 0, 1] : [0, 1, []],
+            result = _.attempt(function() { return fp[methodName](args[0])(args[1])(args[2]); });
+
+        if (typeof result != 'function') {
+          return true;
+        }
+        console.log(methodName, result);
+        return false;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('methods that use `indexOf`');
+
+  (function() {
+    QUnit.test('should work with `fp.indexOf`', function(assert) {
+      assert.expect(10);
+
+      var array = ['a', 'b', 'c'],
+          other = ['b', 'd', 'b'],
+          object = { 'a': 1, 'b': 2, 'c': 2 },
+          actual = fp.difference(array)(other);
+
+      assert.deepEqual(actual, ['a', 'c'], 'fp.difference');
+
+      actual = fp.includes('b')(array);
+      assert.strictEqual(actual, true, 'fp.includes');
+
+      actual = fp.intersection(other)(array);
+      assert.deepEqual(actual, ['b'], 'fp.intersection');
+
+      actual = fp.omit(other)(object);
+      assert.deepEqual(actual, { 'a': 1, 'c': 2 }, 'fp.omit');
+
+      actual = fp.union(other)(array);
+      assert.deepEqual(actual, ['a', 'b', 'c', 'd'], 'fp.union');
+
+      actual = fp.uniq(other);
+      assert.deepEqual(actual, ['b', 'd'], 'fp.uniq');
+
+      actual = fp.uniqBy(_.identity, other);
+      assert.deepEqual(actual, ['b', 'd'], 'fp.uniqBy');
+
+      actual = fp.without(array)(other);
+      assert.deepEqual(actual, ['a', 'c'], 'fp.without');
+
+      actual = fp.xor(other)(array);
+      assert.deepEqual(actual, ['a', 'c', 'd'], 'fp.xor');
+
+      actual = fp.pull('b')(array);
+      assert.deepEqual(actual, ['a', 'c'], 'fp.pull');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('cherry-picked methods');
+
+  (function() {
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(4);
+
+      var args,
+          array = [1, 2, 3],
+          object = { 'a': 1, 'b': 2 },
+          isFIFO = _.keys(object)[0] == 'a',
+          map = convert('map', _.map),
+          reduce = convert('reduce', _.reduce);
+
+      map(function() {
+        args || (args = slice.call(arguments));
+      })(array);
+
+      assert.deepEqual(args, [1]);
+
+      args = undefined;
+      map(function() {
+        args || (args = slice.call(arguments));
+      })(object);
+
+      assert.deepEqual(args, isFIFO ? [1] : [2]);
+
+      args = undefined;
+      reduce(function() {
+        args || (args = slice.call(arguments));
+      })(0)(array);
+
+      assert.deepEqual(args, [0, 1]);
+
+      args = undefined;
+      reduce(function() {
+        args || (args = slice.call(arguments));
+      })(0)(object);
+
+      assert.deepEqual(args, isFIFO ? [0, 1] : [0, 2]);
+    });
+
+    QUnit.test('should not support shortcut fusion', function(assert) {
+      assert.expect(3);
+
+      var array = fp.range(0, LARGE_ARRAY_SIZE),
+          filterCount = 0,
+          mapCount = 0;
+
+      var iteratee = function(value) {
+        mapCount++;
+        return value * value;
+      };
+
+      var predicate = function(value) {
+        filterCount++;
+        return value % 2 == 0;
+      };
+
+      var map1 = convert('map', _.map),
+          filter1 = convert('filter', _.filter),
+          take1 = convert('take', _.take);
+
+      var filter2 = filter1(predicate),
+          map2 = map1(iteratee),
+          take2 = take1(2);
+
+      var combined = fp.flow(map2, filter2, fp.compact, take2);
+
+      assert.deepEqual(combined(array), [4, 16]);
+      assert.strictEqual(filterCount, 200, 'filterCount');
+      assert.strictEqual(mapCount, 200, 'mapCount');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('iteratee shorthands');
+
+  (function() {
+    var objects = [{ 'a': 1, 'b': 2 }, { 'a': 3, 'b': 4 }];
+
+    QUnit.test('should work with "_.matches" shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.filter({ 'a': 3 })(objects), [objects[1]]);
+    });
+
+    QUnit.test('should work with "_.matchesProperty" shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.filter(['a', 3])(objects), [objects[1]]);
+    });
+
+    QUnit.test('should work with "_.property" shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.map('a')(objects), [1, 3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('mutation methods');
+
+  (function() {
+    var array = [1, 2, 3],
+        object = { 'a': 1 },
+        deepObject = { 'a': { 'b': 2, 'c': 3 } };
+
+    QUnit.test('should not mutate values', function(assert) {
+      assert.expect(42);
+
+      function Foo() {}
+      Foo.prototype = { 'b': 2 };
+
+      var value = _.clone(object),
+          actual = fp.assign(value)({ 'b': 2 });
+
+      assert.deepEqual(value, object, 'fp.assign');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.assign');
+
+      value = _.clone(object);
+      actual = fp.assignWith(function(objValue, srcValue) {
+        return srcValue;
+      })(value)({ 'b': 2 });
+
+      assert.deepEqual(value, object, 'fp.assignWith');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.assignWith');
+
+      value = _.clone(object);
+      actual = fp.assignIn(value)(new Foo);
+
+      assert.deepEqual(value, object, 'fp.assignIn');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.assignIn');
+
+      value = _.clone(object);
+      actual = fp.assignInWith(function(objValue, srcValue) {
+        return srcValue;
+      })(value)(new Foo);
+
+      assert.deepEqual(value, object, 'fp.assignInWith');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.assignInWith');
+
+      value = _.clone(object);
+      actual = fp.defaults({ 'a': 2, 'b': 2 })(value);
+
+      assert.deepEqual(value, object, 'fp.defaults');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.defaults');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.defaultsDeep({ 'a': { 'c': 4, 'd': 4 } })(deepObject);
+
+      assert.deepEqual(value, { 'a': { 'b': 2, 'c': 3 } }, 'fp.defaultsDeep');
+      assert.deepEqual(actual, { 'a': { 'b': 2, 'c': 3, 'd': 4 } }, 'fp.defaultsDeep');
+
+      value = _.clone(object);
+      actual = fp.extend(value)(new Foo);
+
+      assert.deepEqual(value, object, 'fp.extend');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.extend');
+
+      value = _.clone(object);
+      actual = fp.extendWith(function(objValue, srcValue) {
+        return srcValue;
+      })(value)(new Foo);
+
+      assert.deepEqual(value, object, 'fp.extendWith');
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 }, 'fp.extendWith');
+
+      value = _.clone(array);
+      actual = fp.fill(1)(2)('*')(value);
+
+      assert.deepEqual(value, array, 'fp.fill');
+      assert.deepEqual(actual, [1, '*', 3], 'fp.fill');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.merge(value)({ 'a': { 'd': 4 } });
+
+      assert.deepEqual(value, { 'a': { 'b': 2, 'c': 3 } }, 'fp.merge');
+      assert.deepEqual(actual, { 'a': { 'b': 2, 'c': 3, 'd': 4 } }, 'fp.merge');
+
+      value = _.cloneDeep(deepObject);
+      value.a.b = [1];
+
+      actual = fp.mergeWith(function(objValue, srcValue) {
+        if (_.isArray(objValue)) {
+          return objValue.concat(srcValue);
+        }
+      }, value, { 'a': { 'b': [2, 3] } });
+
+      assert.deepEqual(value, { 'a': { 'b': [1], 'c': 3 } }, 'fp.mergeWith');
+      assert.deepEqual(actual, { 'a': { 'b': [1, 2, 3], 'c': 3 } }, 'fp.mergeWith');
+
+      value = _.clone(array);
+      actual = fp.pull(2)(value);
+
+      assert.deepEqual(value, array, 'fp.pull');
+      assert.deepEqual(actual, [1, 3], 'fp.pull');
+
+      value = _.clone(array);
+      actual = fp.pullAll([1, 3])(value);
+
+      assert.deepEqual(value, array, 'fp.pullAll');
+      assert.deepEqual(actual, [2], 'fp.pullAll');
+
+      value = _.clone(array);
+      actual = fp.pullAt([0, 2])(value);
+
+      assert.deepEqual(value, array, 'fp.pullAt');
+      assert.deepEqual(actual, [2], 'fp.pullAt');
+
+      value = _.clone(array);
+      actual = fp.remove(function(value) {
+        return value === 2;
+      })(value);
+
+      assert.deepEqual(value, array, 'fp.remove');
+      assert.deepEqual(actual, [1, 3], 'fp.remove');
+
+      value = _.clone(array);
+      actual = fp.reverse(value);
+
+      assert.deepEqual(value, array, 'fp.reverse');
+      assert.deepEqual(actual, [3, 2, 1], 'fp.reverse');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.set('a.b')(3)(value);
+
+      assert.deepEqual(value, deepObject, 'fp.set');
+      assert.deepEqual(actual, { 'a': { 'b': 3, 'c': 3 } }, 'fp.set');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.setWith(Object)('d.e')(4)(value);
+
+      assert.deepEqual(value, deepObject, 'fp.setWith');
+      assert.deepEqual(actual, { 'a': { 'b': 2, 'c': 3 }, 'd': { 'e': 4 } }, 'fp.setWith');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.unset('a.b')(value);
+
+      assert.deepEqual(value, deepObject, 'fp.unset');
+      assert.deepEqual(actual, { 'a': { 'c': 3 } }, 'fp.unset');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.update('a.b')(function(n) { return n * n; })(value);
+
+      assert.deepEqual(value, deepObject, 'fp.update');
+      assert.deepEqual(actual, { 'a': { 'b': 4, 'c': 3 } }, 'fp.update');
+
+      value = _.cloneDeep(deepObject);
+      actual = fp.updateWith(Object)('d.e')(_.constant(4))(value);
+
+      assert.deepEqual(value, deepObject, 'fp.updateWith');
+      assert.deepEqual(actual, { 'a': { 'b': 2, 'c': 3 }, 'd': { 'e': 4 } }, 'fp.updateWith');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('placeholder methods');
+
+  (function() {
+    QUnit.test('should use `fp` as the default placeholder', function(assert) {
+      assert.expect(3);
+
+      var actual = fp.add(fp, 'b')('a');
+      assert.strictEqual(actual, 'ab');
+
+      actual = fp.slice(fp, 2)(1)(['a', 'b', 'c']);
+      assert.deepEqual(actual, ['b']);
+
+      actual = fp.fill(fp, 2)(1, '*')([1, 2, 3]);
+      assert.deepEqual(actual, [1, '*', 3]);
+    });
+
+    QUnit.test('should support `fp.placeholder`', function(assert) {
+      assert.expect(6);
+
+      _.each([[], fp.__], function(ph) {
+        fp.placeholder = ph;
+
+        var actual = fp.add(ph, 'b')('a');
+        assert.strictEqual(actual, 'ab');
+
+        actual = fp.slice(ph, 2)(1)(['a', 'b', 'c']);
+        assert.deepEqual(actual, ['b']);
+
+        actual = fp.fill(ph, 2)(1, '*')([1, 2, 3]);
+        assert.deepEqual(actual, [1, '*', 3]);
+      });
+    });
+
+    _.forOwn(mapping.placeholder, function(truthy, methodName) {
+      var func = fp[methodName];
+
+      QUnit.test('`_.' + methodName + '` should have a `placeholder` property', function(assert) {
+        assert.expect(2);
+
+        assert.ok(_.isObject(func.placeholder));
+        assert.strictEqual(func.placeholder, fp.__);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('set methods');
+
+  (function() {
+    QUnit.test('should only clone objects in `path`', function(assert) {
+      assert.expect(11);
+
+      var object = { 'a': { 'b': 2, 'c': 3 }, 'd': { 'e': 4 } },
+          value = _.cloneDeep(object),
+          actual = fp.set('a.b.c.d', 5, value);
+
+      assert.ok(_.isObject(actual.a.b), 'fp.set');
+      assert.ok(_.isNumber(actual.a.b), 'fp.set');
+
+      assert.strictEqual(actual.a.b.c.d, 5, 'fp.set');
+      assert.strictEqual(actual.d, value.d, 'fp.set');
+
+      value = _.cloneDeep(object);
+      actual = fp.setWith(Object)('[0][1]')('a')(value);
+
+      assert.deepEqual(actual[0], { '1': 'a' }, 'fp.setWith');
+
+      value = _.cloneDeep(object);
+      actual = fp.unset('a.b')(value);
+
+      assert.notOk('b' in actual.a, 'fp.unset');
+      assert.strictEqual(actual.a.c, value.a.c, 'fp.unset');
+
+      value = _.cloneDeep(object);
+      actual = fp.update('a.b')(function(n) { return n * n; })(value);
+
+      assert.strictEqual(actual.a.b, 4, 'fp.update');
+      assert.strictEqual(actual.d, value.d, 'fp.update');
+
+      value = _.cloneDeep(object);
+      actual = fp.updateWith(Object)('[0][1]')(_.constant('a'))(value);
+
+      assert.deepEqual(actual[0], { '1': 'a' }, 'fp.updateWith');
+      assert.strictEqual(actual.d, value.d, 'fp.updateWith');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('with methods');
+
+  (function() {
+    var object = { 'a': 1 };
+
+    QUnit.test('should provide the correct `customizer` arguments', function(assert) {
+      assert.expect(7);
+
+      var args,
+          value = _.clone(object);
+
+      fp.assignWith(function() {
+        args || (args = _.map(arguments, _.cloneDeep));
+      })(value)({ 'b': 2 });
+
+      assert.deepEqual(args, [undefined, 2, 'b', { 'a': 1 }, { 'b': 2 }], 'fp.assignWith');
+
+      args = undefined;
+      value = _.clone(object);
+
+      fp.extendWith(function() {
+        args || (args = _.map(arguments, _.cloneDeep));
+      })(value)({ 'b': 2 });
+
+      assert.deepEqual(args, [undefined, 2, 'b', { 'a': 1 }, { 'b': 2 }], 'fp.extendWith');
+
+      var iteration = 0,
+          objects = [{ 'a': 1 }, { 'a': 2 }],
+          stack = { '__data__': { 'array': [[objects[0], objects[1]]], 'map': null } },
+          expected = [1, 2, 'a', objects[0], objects[1], stack];
+
+      args = undefined;
+
+      fp.isEqualWith(function() {
+        if (++iteration == 2) {
+          args = _.map(arguments, _.cloneDeep);
+        }
+      })(objects[0])(objects[1]);
+
+      args[5] = _.omitBy(args[5], _.isFunction);
+      assert.deepEqual(args, expected, 'fp.isEqualWith');
+
+      args = undefined;
+      stack = { '__data__': { 'array': [], 'map': null } };
+      expected = [2, 1, 'a', objects[1], objects[0], stack];
+
+      fp.isMatchWith(function() {
+        args || (args = _.map(arguments, _.cloneDeep));
+      })(objects[0])(objects[1]);
+
+      args[5] = _.omitBy(args[5], _.isFunction);
+      assert.deepEqual(args, expected, 'fp.isMatchWith');
+
+      args = undefined;
+      value = { 'a': [1] };
+      expected = [[1], [2, 3], 'a', { 'a': [1] }, { 'a': [2, 3] }, stack];
+
+      fp.mergeWith(function() {
+        args || (args = _.map(arguments, _.cloneDeep));
+      })(value)({ 'a': [2, 3] });
+
+      args[5] = _.omitBy(args[5], _.isFunction);
+      assert.deepEqual(args, expected, 'fp.mergeWith');
+
+      args = undefined;
+      value = _.clone(object);
+
+      fp.setWith(function() {
+        args || (args = _.map(arguments, _.cloneDeep));
+      })('b.c')(2)(value);
+
+      assert.deepEqual(args, [undefined, 'b', { 'a': 1 }], 'fp.setWith');
+
+      args = undefined;
+      value = _.clone(object);
+
+      fp.updateWith(function() {
+        args || (args = _.map(arguments, _.cloneDeep));
+      })('b.c')(_.constant(2))(value);
+
+      assert.deepEqual(args, [undefined, 'b', { 'a': 1 }], 'fp.updateWith');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.add and fp.subtract');
+
+  _.each(['add', 'subtract'], function(methodName) {
+    var func = fp[methodName],
+        isAdd = methodName == 'add';
+
+    QUnit.test('`fp.' + methodName + '` should not have `rearg` applied', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func('1')('2'), isAdd ? '12' : -1);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.castArray');
+
+  (function() {
+    QUnit.test('should shallow clone array values', function(assert) {
+      assert.expect(2);
+
+      var array = [1],
+          actual = fp.castArray(array);
+
+      assert.deepEqual(actual, array);
+      assert.notStrictEqual(actual, array);
+    });
+
+    QUnit.test('should not shallow clone non-array values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1 },
+          actual = fp.castArray(object);
+
+      assert.deepEqual(actual, [object]);
+      assert.strictEqual(actual[0], object);
+    });
+
+    QUnit.test('should convert by name', function(assert) {
+      assert.expect(4);
+
+      var array = [1],
+          object = { 'a': 1 },
+          castArray = convert('castArray', _.castArray),
+          actual = castArray(array);
+
+      assert.deepEqual(actual, array);
+      assert.notStrictEqual(actual, array);
+
+      actual = castArray(object);
+      assert.deepEqual(actual, [object]);
+      assert.strictEqual(actual[0], object);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.curry and fp.curryRight');
+
+  _.each(['curry', 'curryRight'], function(methodName) {
+    var func = fp[methodName];
+
+    QUnit.test('`_.' + methodName + '` should only accept a `func` param', function(assert) {
+      assert.expect(1);
+
+      assert.raises(function() { func(1, _.noop); }, TypeError);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.curryN and fp.curryRightN');
+
+  _.each(['curryN', 'curryRightN'], function(methodName) {
+    var func = fp[methodName];
+
+    QUnit.test('`_.' + methodName + '` should accept an `arity` param', function(assert) {
+      assert.expect(1);
+
+      var actual = func(1)(function(a, b) { return [a, b]; })('a');
+      assert.deepEqual(actual, ['a', undefined]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.difference');
+
+  (function() {
+    QUnit.test('should return the elements of the first array not included in the second array', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.difference([1, 2])([2, 3]), [1]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.divide and fp.multiply');
+
+  _.each(['divide', 'multiply'], function(methodName) {
+    var func = fp[methodName],
+        isDivide = methodName == 'divide';
+
+    QUnit.test('`fp.' + methodName + '` should not have `rearg` applied', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func('2')('4'), isDivide ? 0.5 : 8);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.extend');
+
+  (function() {
+    QUnit.test('should convert by name', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype = { 'b': 2 };
+
+      var object = { 'a': 1 },
+          extend = convert('extend', _.extend),
+          value = _.clone(object),
+          actual = extend(value)(new Foo);
+
+      assert.deepEqual(value, object);
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.fill');
+
+  (function() {
+    QUnit.test('should have an argument order of `start`, `end`, then `value`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(fp.fill(1)(2)('*')(array), [1, '*', 3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.flatMapDepth');
+
+  (function() {
+    QUnit.test('should have an argument order of `iteratee`, `depth`, then `collection`', function(assert) {
+      assert.expect(2);
+
+      function duplicate(n) {
+        return [[[n, n]]];
+      }
+
+      var array = [1, 2],
+          object = { 'a': 1, 'b': 2 },
+          expected = [[1, 1], [2, 2]];
+
+      assert.deepEqual(fp.flatMapDepth(duplicate)(2)(array), expected);
+      assert.deepEqual(fp.flatMapDepth(duplicate)(2)(object), expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.flow and fp.flowRight');
+
+  _.each(['flow', 'flowRight'], function(methodName) {
+    var func = fp[methodName],
+        isFlow = methodName == 'flow';
+
+    QUnit.test('`fp.' + methodName + '` should support shortcut fusion', function(assert) {
+      assert.expect(6);
+
+      var filterCount,
+          mapCount,
+          array = fp.range(0, LARGE_ARRAY_SIZE);
+
+      var iteratee = function(value) {
+        mapCount++;
+        return value * value;
+      };
+
+      var predicate = function(value) {
+        filterCount++;
+        return value % 2 == 0;
+      };
+
+      var filter = fp.filter(predicate),
+          map = fp.map(iteratee),
+          take = fp.take(2);
+
+      _.times(2, function(index) {
+        var combined = isFlow
+          ? func(map, filter, fp.compact, take)
+          : func(take, fp.compact, filter, map);
+
+        filterCount = mapCount = 0;
+
+        if (WeakMap && WeakMap.name) {
+          assert.deepEqual(combined(array), [4, 16]);
+          assert.strictEqual(filterCount, 5, 'filterCount');
+          assert.strictEqual(mapCount, 5, 'mapCount');
+        }
+        else {
+          skipAssert(assert, 3);
+        }
+      });
+    });
+  });
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.getOr');
+
+  (function() {
+    QUnit.test('should accept a `defaultValue` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.getOr('default')('path')({});
+      assert.strictEqual(actual, 'default');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.gt and fp.gte');
+
+  _.each(['gt', 'gte'], function(methodName) {
+    var func = fp[methodName];
+
+    QUnit.test('`fp.' + methodName + '` should have `rearg` applied', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func(2)(1), true);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.inRange');
+
+  (function() {
+    QUnit.test('should have an argument order of `start`, `end`, then `value`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(fp.inRange(2)(4)(3), true);
+      assert.strictEqual(fp.inRange(-2)(-6)(-3), true);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.invoke');
+
+  (function() {
+    QUnit.test('should not accept an `args` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.invoke('toUpperCase')('a');
+      assert.strictEqual(actual, 'A');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.invokeMap');
+
+  (function() {
+    QUnit.test('should not accept an `args` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.invokeMap('toUpperCase')(['a', 'b']);
+      assert.deepEqual(actual, ['A', 'B']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.invokeArgs');
+
+  (function() {
+    QUnit.test('should accept an `args` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.invokeArgs('concat')(['b', 'c'])('a');
+      assert.strictEqual(actual, 'abc');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.invokeArgsMap');
+
+  (function() {
+    QUnit.test('should accept an `args` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.invokeArgsMap('concat')(['b', 'c'])(['a', 'A']);
+      assert.deepEqual(actual, ['abc', 'Abc']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.iteratee');
+
+  (function() {
+    QUnit.test('should return a iteratee with capped params', function(assert) {
+      assert.expect(1);
+
+      var func = fp.iteratee(function(a, b, c) { return [a, b, c]; }, 3);
+      assert.deepEqual(func(1, 2, 3), [1, undefined, undefined]);
+    });
+
+    QUnit.test('should convert by name', function(assert) {
+      assert.expect(1);
+
+      var iteratee = convert('iteratee', _.iteratee),
+          func = iteratee(function(a, b, c) { return [a, b, c]; }, 3);
+
+      assert.deepEqual(func(1, 2, 3), [1, undefined, undefined]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.lt and fp.lte');
+
+  _.each(['lt', 'lte'], function(methodName) {
+    var func = fp[methodName];
+
+    QUnit.test('`fp.' + methodName + '` should have `rearg` applied', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func(1)(2), true);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.mapKeys');
+
+  (function() {
+    QUnit.test('should only provide `key` to `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var args,
+          object = { 'a': 1 };
+
+      fp.mapKeys(function() {
+        args || (args = slice.call(arguments));
+      }, object);
+
+      assert.deepEqual(args, ['a']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.maxBy and fp.minBy');
+
+  _.each(['maxBy', 'minBy'], function(methodName) {
+    var array = [1, 2, 3],
+        func = fp[methodName],
+        isMax = methodName == 'maxBy';
+
+    QUnit.test('`fp.' + methodName + '` should work with an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var actual = func(function(num) {
+        return -num;
+      })(array);
+
+      assert.strictEqual(actual, isMax ? 1 : 3);
+    });
+
+    QUnit.test('`fp.' + methodName + '` should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      func(function() {
+        args || (args = slice.call(arguments));
+      })(array);
+
+      assert.deepEqual(args, [1]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.mixin');
+
+  (function() {
+    var source = { 'a': _.noop };
+
+    QUnit.test('should mixin static methods but not prototype methods', function(assert) {
+      assert.expect(2);
+
+      fp.mixin(source);
+
+      assert.strictEqual(typeof fp.a, 'function');
+      assert.notOk('a' in fp.prototype);
+
+      delete fp.a;
+      delete fp.prototype.a;
+    });
+
+    QUnit.test('should not assign inherited `source` methods', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.a = _.noop;
+      fp.mixin(new Foo);
+
+      assert.notOk('a' in fp);
+      assert.notOk('a' in fp.prototype);
+
+      delete fp.a;
+      delete fp.prototype.a;
+    });
+
+    QUnit.test('should not remove existing prototype methods', function(assert) {
+      assert.expect(2);
+
+      var each1 = fp.each,
+          each2 = fp.prototype.each;
+
+      fp.mixin({ 'each': source.a });
+
+      assert.strictEqual(fp.each, source.a);
+      assert.strictEqual(fp.prototype.each, each2);
+
+      fp.each = each1;
+      fp.prototype.each = each2;
+    });
+
+    QUnit.test('should not export to the global when `source` is not an object', function(assert) {
+      assert.expect(2);
+
+      var props = _.without(_.keys(_), '_');
+
+      _.times(2, function(index) {
+        fp.mixin.apply(fp, index ? [1] : []);
+
+        assert.ok(_.every(props, function(key) {
+          return root[key] !== fp[key];
+        }));
+
+        _.each(props, function(key) {
+          if (root[key] === fp[key]) {
+            delete root[key];
+          }
+        });
+      });
+    });
+
+    QUnit.test('should convert by name', function(assert) {
+      assert.expect(3);
+
+      var object = { 'mixin': convert('mixin', _.mixin) };
+
+      function Foo() {}
+      Foo.mixin = object.mixin;
+      Foo.mixin(source);
+
+      assert.strictEqual(typeof Foo.a, 'function');
+      assert.notOk('a' in Foo.prototype);
+
+      object.mixin(source);
+      assert.strictEqual(typeof object.a, 'function');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.over');
+
+  (function() {
+    QUnit.test('should not cap iteratee args', function(assert) {
+      assert.expect(2);
+
+      _.each([fp.over, convert('over', _.over)], function(func) {
+        var over = func([Math.max, Math.min]);
+        assert.deepEqual(over(1, 2, 3, 4), [4, 1]);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.omitBy and fp.pickBy');
+
+  _.each(['omitBy', 'pickBy'], function(methodName) {
+    var func = fp[methodName];
+
+    QUnit.test('`fp.' + methodName + '` should provide `value` and `key` to `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var args,
+          object = { 'a': 1 };
+
+      func(function() {
+        args || (args = slice.call(arguments));
+      })(object);
+
+      assert.deepEqual(args, [1, 'a']);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('padChars methods');
+
+  _.each(['padChars', 'padCharsStart', 'padCharsEnd'], function(methodName) {
+    var func = fp[methodName],
+        isPad = methodName == 'padChars',
+        isStart = methodName == 'padCharsStart';
+
+    QUnit.test('`_.' + methodName + '` should truncate pad characters to fit the pad length', function(assert) {
+      assert.expect(1);
+
+      if (isPad) {
+        assert.strictEqual(func('_-')(8)('abc'), '_-abc_-_');
+      } else {
+        assert.strictEqual(func('_-')(6)('abc'), isStart ? '_-_abc' : 'abc_-_');
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.partial and fp.partialRight');
+
+  _.each(['partial', 'partialRight'], function(methodName) {
+    var func = fp[methodName],
+        isPartial = methodName == 'partial';
+
+    QUnit.test('`_.' + methodName + '` should accept an `args` param', function(assert) {
+      assert.expect(1);
+
+      var expected = isPartial ? [1, 2, 3] : [0, 1, 2];
+
+      var actual = func(function(a, b, c) {
+        return [a, b, c];
+      })([1, 2])(isPartial ? 3 : 0);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert by name', function(assert) {
+      assert.expect(2);
+
+      var expected = isPartial ? [1, 2, 3] : [0, 1, 2],
+          par = convert(methodName, _[methodName]),
+          ph = par.placeholder;
+
+      var actual = par(function(a, b, c) {
+        return [a, b, c];
+      })([1, 2])(isPartial ? 3 : 0);
+
+      assert.deepEqual(actual, expected);
+
+      actual = par(function(a, b, c) {
+        return [a, b, c];
+      })([ph, 2])(isPartial ? 1 : 0, isPartial ? 3 : 1);
+
+      assert.deepEqual(actual, expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.random');
+
+  (function() {
+    var array = Array(1000);
+
+    QUnit.test('should support a `min` and `max` argument', function(assert) {
+      assert.expect(1);
+
+      var min = 5,
+          max = 10;
+
+      assert.ok(_.some(array, function() {
+        var result = fp.random(min)(max);
+        return result >= min && result <= max;
+      }));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.range');
+
+  (function() {
+    QUnit.test('should have an argument order of `start` then `end`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.range(1)(4), [1, 2, 3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.reduce and fp.reduceRight');
+
+  _.each(['reduce', 'reduceRight'], function(methodName) {
+    var func = fp[methodName],
+        isReduce = methodName == 'reduce';
+
+    QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an array', function(assert) {
+      assert.expect(1);
+
+      var args,
+          array = [1, 2, 3];
+
+      func(function() {
+        args || (args = slice.call(arguments));
+      })(0)(array);
+
+      assert.deepEqual(args, isReduce ? [0, 1] : [0, 3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments when iterating an object', function(assert) {
+      assert.expect(1);
+
+      var args,
+          object = { 'a': 1, 'b': 2 },
+          isFIFO = _.keys(object)[0] == 'a';
+
+      var expected = isFIFO
+        ? (isReduce ? [0, 1] : [0, 2])
+        : (isReduce ? [0, 2] : [0, 1]);
+
+      func(function() {
+        args || (args = slice.call(arguments));
+      })(0)(object);
+
+      assert.deepEqual(args, expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.restFrom');
+
+  (function() {
+    QUnit.test('should accept a `start` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.restFrom(2)(function() {
+        return slice.call(arguments);
+      })('a', 'b', 'c', 'd');
+
+      assert.deepEqual(actual, ['a', 'b', ['c', 'd']]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.runInContext');
+
+  (function() {
+    QUnit.test('should return a converted lodash instance', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(typeof fp.runInContext({}).curryN, 'function');
+    });
+
+    QUnit.test('should convert by name', function(assert) {
+      assert.expect(1);
+
+      var runInContext = convert('runInContext', _.runInContext);
+      assert.strictEqual(typeof runInContext({}).curryN, 'function');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.spreadFrom');
+
+  (function() {
+    QUnit.test('should accept a `start` param', function(assert) {
+      assert.expect(1);
+
+      var actual = fp.spreadFrom(2)(function() {
+        return slice.call(arguments);
+      })('a', 'b', ['c', 'd']);
+
+      assert.deepEqual(actual, ['a', 'b', 'c', 'd']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('trimChars methods');
+
+  _.each(['trimChars', 'trimCharsStart', 'trimCharsEnd'], function(methodName, index) {
+    var func = fp[methodName],
+        parts = [];
+
+    if (index != 2) {
+      parts.push('leading');
+    }
+    if (index != 1) {
+      parts.push('trailing');
+    }
+    parts = parts.join(' and ');
+
+    QUnit.test('`_.' + methodName + '` should remove ' + parts + ' `chars`', function(assert) {
+      assert.expect(1);
+
+      var string = '-_-a-b-c-_-',
+          expected = (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : '');
+
+      assert.strictEqual(func('_-')(string), expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.uniqBy');
+
+  (function() {
+    var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }];
+
+    QUnit.test('should work with an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = objects.slice(0, 3);
+
+      var actual = fp.uniqBy(function(object) {
+        return object.a;
+      })(objects);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      fp.uniqBy(function() {
+        args || (args = slice.call(arguments));
+      })(objects);
+
+      assert.deepEqual(args, [objects[0]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.zip');
+
+  (function() {
+    QUnit.test('should zip together two arrays', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.zip([1, 2])([3, 4]), [[1, 3], [2, 4]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.zipObject');
+
+  (function() {
+    QUnit.test('should zip together key/value arrays into an object', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(fp.zipObject(['a', 'b'])([1, 2]), { 'a': 1, 'b': 2 });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('fp.zipWith');
+
+  (function() {
+    QUnit.test('should zip arrays combining grouped elements with `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var array1 = [1, 2, 3],
+          array2 = [4, 5, 6];
+
+      var actual = fp.zipWith(function(a, b) {
+        return a + b;
+      })(array1)(array2);
+
+      assert.deepEqual(actual, [5, 7, 9]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.config.asyncRetries = 10;
+  QUnit.config.hidepassed = true;
+
+  if (!document) {
+    QUnit.config.noglobals = true;
+    QUnit.load();
+  }
+}.call(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/test.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/test.js
new file mode 100644
index 0000000..0df49ab
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/test.js
@@ -0,0 +1,26225 @@
+;(function() {
+
+  /** Used as a safe reference for `undefined` in pre-ES5 environments. */
+  var undefined;
+
+  /** Used to detect when a function becomes hot. */
+  var HOT_COUNT = 150;
+
+  /** Used as the size to cover large array optimizations. */
+  var LARGE_ARRAY_SIZE = 200;
+
+  /** Used as the `TypeError` message for "Functions" methods. */
+  var FUNC_ERROR_TEXT = 'Expected a function';
+
+  /** Used as references for various `Number` constants. */
+  var MAX_SAFE_INTEGER = 9007199254740991,
+      MAX_INTEGER = 1.7976931348623157e+308;
+
+  /** Used as references for the maximum length and index of an array. */
+  var MAX_ARRAY_LENGTH = 4294967295,
+      MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1;
+
+  /** `Object#toString` result references. */
+  var funcTag = '[object Function]',
+      numberTag = '[object Number]',
+      objectTag = '[object Object]';
+
+  /** Used as a reference to the global object. */
+  var root = (typeof global == 'object' && global) || this;
+
+  /** Used to store lodash to test for bad extensions/shims. */
+  var lodashBizarro = root.lodashBizarro;
+
+  /** Used for native method references. */
+  var arrayProto = Array.prototype,
+      funcProto = Function.prototype,
+      objectProto = Object.prototype,
+      numberProto = Number.prototype,
+      stringProto = String.prototype;
+
+  /** Method and object shortcuts. */
+  var phantom = root.phantom,
+      process = root.process,
+      amd = root.define && define.amd,
+      argv = process && process.argv,
+      defineProperty = Object.defineProperty,
+      document = !phantom && root.document,
+      body = root.document && root.document.body,
+      create = Object.create,
+      fnToString = funcProto.toString,
+      freeze = Object.freeze,
+      getSymbols = Object.getOwnPropertySymbols,
+      identity = function(value) { return value; },
+      JSON = root.JSON,
+      noop = function() {},
+      objToString = objectProto.toString,
+      params = argv,
+      push = arrayProto.push,
+      realm = {},
+      slice = arrayProto.slice;
+
+  var ArrayBuffer = root.ArrayBuffer,
+      Buffer = root.Buffer,
+      Promise = root.Promise,
+      Map = root.Map,
+      Set = root.Set,
+      Symbol = root.Symbol,
+      Uint8Array = root.Uint8Array,
+      WeakMap = root.WeakMap,
+      WeakSet = root.WeakSet;
+
+  var arrayBuffer = ArrayBuffer ? new ArrayBuffer(2) : undefined,
+      map = Map ? new Map : undefined,
+      promise = Promise ? Promise.resolve(1) : undefined,
+      set = Set ? new Set : undefined,
+      symbol = Symbol ? Symbol('a') : undefined,
+      weakMap = WeakMap ? new WeakMap : undefined,
+      weakSet = WeakSet ? new WeakSet : undefined;
+
+  /** Math helpers. */
+  var add = function(x, y) { return x + y; },
+      doubled = function(n) { return n * 2; },
+      isEven = function(n) { return n % 2 == 0; },
+      square = function(n) { return n * n; };
+
+  /** Constant functions. */
+  var alwaysA = function() { return 'a'; },
+      alwaysB = function() { return 'b'; },
+      alwaysC = function() { return 'c'; };
+
+  var alwaysTrue = function() { return true; },
+      alwaysFalse = function() { return false; };
+
+  var alwaysNaN = function() { return NaN; },
+      alwaysNull = function() { return null; };
+
+  var alwaysZero = function() { return 0; },
+      alwaysOne = function() { return 1; },
+      alwaysTwo = function() { return 2; },
+      alwaysThree = function() { return 3; },
+      alwaysFour = function() { return 4; };
+
+  var alwaysEmptyArray = function() { return []; },
+      alwaysEmptyObject = function() { return {}; },
+      alwaysEmptyString = function() { return ''; };
+
+  /** List of latin-1 supplementary letters to basic latin letters. */
+  var burredLetters = [
+    '\xc0', '\xc1', '\xc2', '\xc3', '\xc4', '\xc5', '\xc6', '\xc7', '\xc8', '\xc9', '\xca', '\xcb', '\xcc', '\xcd', '\xce',
+    '\xcf', '\xd0', '\xd1', '\xd2', '\xd3', '\xd4', '\xd5', '\xd6', '\xd8', '\xd9', '\xda', '\xdb', '\xdc', '\xdd', '\xde',
+    '\xdf', '\xe0', '\xe1', '\xe2', '\xe3', '\xe4', '\xe5', '\xe6', '\xe7', '\xe8', '\xe9', '\xea', '\xeb', '\xec', '\xed', '\xee',
+    '\xef', '\xf0', '\xf1', '\xf2', '\xf3', '\xf4', '\xf5', '\xf6', '\xf8', '\xf9', '\xfa', '\xfb', '\xfc', '\xfd', '\xfe', '\xff'
+  ];
+
+  /** List of combining diacritical marks. */
+  var comboMarks = [
+    '\u0300', '\u0301', '\u0302', '\u0303', '\u0304', '\u0305', '\u0306', '\u0307', '\u0308', '\u0309', '\u030a', '\u030b', '\u030c', '\u030d', '\u030e', '\u030f',
+    '\u0310', '\u0311', '\u0312', '\u0313', '\u0314', '\u0315', '\u0316', '\u0317', '\u0318', '\u0319', '\u031a', '\u031b', '\u031c', '\u031d', '\u031e', '\u031f',
+    '\u0320', '\u0321', '\u0322', '\u0323', '\u0324', '\u0325', '\u0326', '\u0327', '\u0328', '\u0329', '\u032a', '\u032b', '\u032c', '\u032d', '\u032e', '\u032f',
+    '\u0330', '\u0331', '\u0332', '\u0333', '\u0334', '\u0335', '\u0336', '\u0337', '\u0338', '\u0339', '\u033a', '\u033b', '\u033c', '\u033d', '\u033e', '\u033f',
+    '\u0340', '\u0341', '\u0342', '\u0343', '\u0344', '\u0345', '\u0346', '\u0347', '\u0348', '\u0349', '\u034a', '\u034b', '\u034c', '\u034d', '\u034e', '\u034f',
+    '\u0350', '\u0351', '\u0352', '\u0353', '\u0354', '\u0355', '\u0356', '\u0357', '\u0358', '\u0359', '\u035a', '\u035b', '\u035c', '\u035d', '\u035e', '\u035f',
+    '\u0360', '\u0361', '\u0362', '\u0363', '\u0364', '\u0365', '\u0366', '\u0367', '\u0368', '\u0369', '\u036a', '\u036b', '\u036c', '\u036d', '\u036e', '\u036f',
+    '\ufe20', '\ufe21', '\ufe22', '\ufe23'
+  ];
+
+  /** List of `burredLetters` translated to basic latin letters. */
+  var deburredLetters = [
+    'A',  'A', 'A', 'A', 'A', 'A', 'Ae', 'C',  'E', 'E', 'E', 'E', 'I', 'I', 'I',
+    'I',  'D', 'N', 'O', 'O', 'O', 'O',  'O',  'O', 'U', 'U', 'U', 'U', 'Y', 'Th',
+    'ss', 'a', 'a', 'a', 'a', 'a', 'a',  'ae', 'c', 'e', 'e', 'e', 'e', 'i', 'i',  'i',
+    'i',  'd', 'n', 'o', 'o', 'o', 'o',  'o',  'o', 'u', 'u', 'u', 'u', 'y', 'th', 'y'
+  ];
+
+  /** Used to provide falsey values to methods. */
+  var falsey = [, null, undefined, false, 0, NaN, ''];
+
+  /** Used to specify the emoji style glyph variant of characters. */
+  var emojiVar = '\ufe0f';
+
+  /** Used to provide empty values to methods. */
+  var empties = [[], {}].concat(falsey.slice(1));
+
+  /** Used to test error objects. */
+  var errors = [
+    new Error,
+    new EvalError,
+    new RangeError,
+    new ReferenceError,
+    new SyntaxError,
+    new TypeError,
+    new URIError
+  ];
+
+  /** List of fitzpatrick modifiers. */
+  var fitzModifiers = [
+    '\ud83c\udffb',
+    '\ud83c\udffc',
+    '\ud83c\udffd',
+    '\ud83c\udffe',
+    '\ud83c\udfff'
+  ];
+
+  /** Used to provide primitive values to methods. */
+  var primitives = [null, undefined, false, true, 1, NaN, 'a'];
+
+  /** Used to check whether methods support typed arrays. */
+  var typedArrays = [
+    'Float32Array',
+    'Float64Array',
+    'Int8Array',
+    'Int16Array',
+    'Int32Array',
+    'Uint8Array',
+    'Uint8ClampedArray',
+    'Uint16Array',
+    'Uint32Array'
+  ];
+
+  /** Used to check whether methods support array views. */
+  var arrayViews = typedArrays.concat('DataView');
+
+  /** The file path of the lodash file to test. */
+  var filePath = (function() {
+    var min = 2,
+        result = params || [];
+
+    if (phantom) {
+      min = 0;
+      result = params = phantom.args || require('system').args;
+    }
+    var last = result[result.length - 1];
+    result = (result.length > min && !/test(?:\.js)?$/.test(last)) ? last : '../lodash.js';
+
+    if (!amd) {
+      try {
+        result = require('fs').realpathSync(result);
+      } catch (e) {}
+
+      try {
+        result = require.resolve(result);
+      } catch (e) {}
+    }
+    return result;
+  }());
+
+  /** The `ui` object. */
+  var ui = root.ui || (root.ui = {
+    'buildPath': filePath,
+    'loaderPath': '',
+    'isModularize': /\b(?:amd|commonjs|es|node|npm|(index|main)\.js)\b/.test(filePath),
+    'isStrict': /\bes\b/.test(filePath),
+    'urlParams': {}
+  });
+
+  /** The basename of the lodash file to test. */
+  var basename = /[\w.-]+$/.exec(filePath)[0];
+
+  /** Used to indicate testing a modularized build. */
+  var isModularize = ui.isModularize;
+
+  /** Detect if testing `npm` modules. */
+  var isNpm = isModularize && /\bnpm\b/.test([ui.buildPath, ui.urlParams.build]);
+
+  /** Detect if running in PhantomJS. */
+  var isPhantom = phantom || (typeof callPhantom == 'function');
+
+  /** Detect if lodash is in strict mode. */
+  var isStrict = ui.isStrict;
+
+  /*--------------------------------------------------------------------------*/
+
+  // Leak to avoid sporadic `noglobals` fails on Edge in Sauce Labs.
+  root.msWDfn = undefined;
+
+  // Exit early if going to run tests in a PhantomJS web page.
+  if (phantom && isModularize) {
+    var page = require('webpage').create();
+
+    page.onCallback = function(details) {
+      var coverage = details.coverage;
+      if (coverage) {
+        var fs = require('fs'),
+            cwd = fs.workingDirectory,
+            sep = fs.separator;
+
+        fs.write([cwd, 'coverage', 'coverage.json'].join(sep), JSON.stringify(coverage));
+      }
+      phantom.exit(details.failed ? 1 : 0);
+    };
+
+    page.onConsoleMessage = function(message) {
+      console.log(message);
+    };
+
+    page.onInitialized = function() {
+      page.evaluate(function() {
+        document.addEventListener('DOMContentLoaded', function() {
+          QUnit.done(function(details) {
+            details.coverage = window.__coverage__;
+            callPhantom(details);
+          });
+        });
+      });
+    };
+
+    page.open(filePath, function(status) {
+      if (status != 'success') {
+        console.log('PhantomJS failed to load page: ' + filePath);
+        phantom.exit(1);
+      }
+    });
+
+    console.log('test.js invoked with arguments: ' + JSON.stringify(slice.call(params)));
+    return;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /** Used to test Web Workers. */
+  var Worker = !(ui.isForeign || ui.isSauceLabs || isModularize) &&
+    (document && document.origin != 'null') && root.Worker;
+
+  /** Used to test host objects in IE. */
+  try {
+    var xml = new ActiveXObject('Microsoft.XMLDOM');
+  } catch (e) {}
+
+  /** Poison the free variable `root` in Node.js */
+  try {
+    defineProperty(global.root, 'root', {
+      'configurable': false,
+      'enumerable': false,
+      'get': function() { throw new ReferenceError; }
+    });
+  } catch (e) {}
+
+  /** Use a single "load" function. */
+  var load = (!amd && typeof require == 'function')
+    ? require
+    : noop;
+
+  /** The unit testing framework. */
+  var QUnit = root.QUnit || (root.QUnit = load('../node_modules/qunitjs/qunit/qunit.js'));
+
+  /** Load stable Lodash and QUnit Extras. */
+  var lodashStable = root.lodashStable;
+  if (!lodashStable) {
+    try {
+      lodashStable = load('../node_modules/lodash/lodash.js');
+    } catch (e) {
+      console.log('Error: The stable lodash dev dependency should be at least a version behind master branch.');
+      return;
+    }
+    lodashStable = lodashStable.noConflict();
+  }
+  lodashStable = lodashStable.runInContext(root);
+
+  var QUnitExtras = load('../node_modules/qunit-extras/qunit-extras.js');
+  if (QUnitExtras) {
+    QUnitExtras.runInContext(root);
+  }
+
+  /** The `lodash` function to test. */
+  var _ = root._ || (root._ = (
+    _ = load(filePath),
+    _ = _._ || (isStrict = ui.isStrict = isStrict || 'default' in _, _['default']) || _,
+    (_.runInContext ? _.runInContext(root) : _)
+  ));
+
+  /** Used to detect instrumented istanbul code coverage runs. */
+  var coverage = root.__coverage__ || root[lodashStable.findKey(root, function(value, key) {
+    return /^(?:\$\$cov_\d+\$\$)$/.test(key);
+  })];
+
+  /** Used to test generator functions. */
+  var generator = lodashStable.attempt(function() {
+    return Function('return function*(){}');
+  });
+
+  /** Used to restore the `_` reference. */
+  var oldDash = root._;
+
+  /**
+   * Used to check for problems removing whitespace. For a whitespace reference,
+   * see [V8's unit test](https://code.google.com/p/v8/source/browse/branches/bleeding_edge/test/mjsunit/whitespaces.js).
+   */
+  var whitespace = lodashStable.filter([
+    // Basic whitespace characters.
+    ' ', '\t', '\x0b', '\f', '\xa0', '\ufeff',
+
+    // Line terminators.
+    '\n', '\r', '\u2028', '\u2029',
+
+    // Unicode category "Zs" space separators.
+    '\u1680', '\u180e', '\u2000', '\u2001', '\u2002', '\u2003', '\u2004', '\u2005',
+    '\u2006', '\u2007', '\u2008', '\u2009', '\u200a', '\u202f', '\u205f', '\u3000'
+  ],
+  function(chr) { return /\s/.exec(chr); })
+  .join('');
+
+  /**
+   * Creates a custom error object.
+   *
+   * @private
+   * @constructor
+   * @param {string} message The error message.
+   */
+  function CustomError(message) {
+    this.name = 'CustomError';
+    this.message = message;
+  }
+
+  CustomError.prototype = lodashStable.create(Error.prototype, {
+    'constructor': CustomError
+  });
+
+  /**
+   * Removes all own enumerable string keyed properties from a given object.
+   *
+   * @private
+   * @param {Object} object The object to empty.
+   */
+  function emptyObject(object) {
+    lodashStable.forOwn(object, function(value, key, object) {
+      delete object[key];
+    });
+  }
+
+  /**
+   * Extracts the unwrapped value from its wrapper.
+   *
+   * @private
+   * @param {Object} wrapper The wrapper to unwrap.
+   * @returns {*} Returns the unwrapped value.
+   */
+  function getUnwrappedValue(wrapper) {
+    var index = -1,
+        actions = wrapper.__actions__,
+        length = actions.length,
+        result = wrapper.__wrapped__;
+
+    while (++index < length) {
+      var args = [result],
+          action = actions[index];
+
+      push.apply(args, action.args);
+      result = action.func.apply(action.thisArg, args);
+    }
+    return result;
+  }
+
+  /**
+   * Sets a non-enumerable property value on `object`.
+   *
+   * Note: This function is used to avoid a bug in older versions of V8 where
+   * overwriting non-enumerable built-ins makes them enumerable.
+   * See https://code.google.com/p/v8/issues/detail?id=1623
+   *
+   * @private
+   * @param {Object} object The object modify.
+   * @param {string} key The name of the property to set.
+   * @param {*} value The property value.
+   */
+  function setProperty(object, key, value) {
+    try {
+      defineProperty(object, key, {
+        'configurable': true,
+        'enumerable': false,
+        'writable': true,
+        'value': value
+      });
+    } catch (e) {
+      object[key] = value;
+    }
+    return object;
+  }
+
+  /**
+   * Skips a given number of tests with a passing result.
+   *
+   * @private
+   * @param {Object} assert The QUnit assert object.
+   * @param {number} [count=1] The number of tests to skip.
+   */
+  function skipAssert(assert, count) {
+    count || (count = 1);
+    while (count--) {
+      assert.ok(true, 'test skipped');
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Add bizarro values.
+  (function() {
+    if (document || (typeof require != 'function')) {
+      return;
+    }
+    var nativeString = fnToString.call(toString),
+        reToString = /toString/g;
+
+    function createToString(funcName) {
+      return lodashStable.constant(nativeString.replace(reToString, funcName));
+    }
+
+    // Allow bypassing native checks.
+    setProperty(funcProto, 'toString', function wrapper() {
+      setProperty(funcProto, 'toString', fnToString);
+      var result = lodashStable.has(this, 'toString') ? this.toString() : fnToString.call(this);
+      setProperty(funcProto, 'toString', wrapper);
+      return result;
+    });
+
+    // Add prototype extensions.
+    funcProto._method = noop;
+
+    // Set bad shims.
+    setProperty(Object, 'create', (function() {
+      function object() {}
+      return function(prototype) {
+        if (lodashStable.isObject(prototype)) {
+          object.prototype = prototype;
+          var result = new object;
+          object.prototype = undefined;
+        }
+        return result || {};
+      };
+    }()));
+
+    setProperty(Object, 'getOwnPropertySymbols', undefined);
+
+    var _propertyIsEnumerable = objectProto.propertyIsEnumerable;
+    setProperty(objectProto, 'propertyIsEnumerable', function(key) {
+      return !(key == 'valueOf' && this && this.valueOf === 1) && _propertyIsEnumerable.call(this, key);
+    });
+
+    if (Buffer) {
+      defineProperty(root, 'Buffer', {
+        'configurable': true,
+        'enumerable': true,
+        'get': function get() {
+          var caller = get.caller,
+              name = caller ? caller.name : '';
+
+          if (!(name == 'runInContext' || name.length == 1 || /\b_\.isBuffer\b/.test(caller))) {
+            return Buffer;
+          }
+        }
+      });
+    }
+    if (Map) {
+      setProperty(root, 'Map', (function() {
+        var count = 0;
+        return function() {
+          if (count++) {
+            return new Map;
+          }
+          setProperty(root, 'Map', Map);
+          return {};
+        };
+      }()));
+
+      setProperty(root.Map, 'toString', createToString('Map'));
+    }
+    setProperty(root, 'Promise', noop);
+    setProperty(root, 'Set', noop);
+    setProperty(root, 'Symbol', undefined);
+    setProperty(root, 'WeakMap', noop);
+
+    // Fake `WinRTError`.
+    setProperty(root, 'WinRTError', Error);
+
+    // Clear cache so lodash can be reloaded.
+    emptyObject(require.cache);
+
+    // Load lodash and expose it to the bad extensions/shims.
+    lodashBizarro = (lodashBizarro = require(filePath))._ || lodashBizarro['default'] || lodashBizarro;
+    root._ = oldDash;
+
+    // Restore built-in methods.
+    setProperty(Object, 'create', create);
+    setProperty(objectProto, 'propertyIsEnumerable', _propertyIsEnumerable);
+    setProperty(root, 'Buffer', Buffer);
+
+    if (getSymbols) {
+      Object.getOwnPropertySymbols = getSymbols;
+    } else {
+      delete Object.getOwnPropertySymbols;
+    }
+    if (Map) {
+      setProperty(root, 'Map', Map);
+    } else {
+      delete root.Map;
+    }
+    if (Promise) {
+      setProperty(root, 'Promise', Promise);
+    } else {
+      delete root.Promise;
+    }
+    if (Set) {
+      setProperty(root, 'Set', Set);
+    } else {
+      delete root.Set;
+    }
+    if (Symbol) {
+      setProperty(root, 'Symbol', Symbol);
+    } else {
+      delete root.Symbol;
+    }
+    if (WeakMap) {
+      setProperty(root, 'WeakMap', WeakMap);
+    } else {
+      delete root.WeakMap;
+    }
+    delete root.WinRTError;
+    delete funcProto._method;
+  }());
+
+  // Add other realm values from the `vm` module.
+  lodashStable.attempt(function() {
+    lodashStable.assign(realm, require('vm').runInNewContext([
+      '(function() {',
+      '  var noop = function() {},',
+      '      root = this;',
+      '',
+      '  var object = {',
+      "    'ArrayBuffer': root.ArrayBuffer,",
+      "    'arguments': (function() { return arguments; }(1, 2, 3)),",
+      "    'array': [1],",
+      "    'arrayBuffer': root.ArrayBuffer ? new root.ArrayBuffer : undefined,",
+      "    'boolean': Object(false),",
+      "    'date': new Date,",
+      "    'errors': [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError],",
+      "    'function': noop,",
+      "    'map': root.Map ? new root.Map : undefined,",
+      "    'nan': NaN,",
+      "    'null': null,",
+      "    'number': Object(0),",
+      "    'object': { 'a': 1 },",
+      "    'promise': root.Promise ? Promise.resolve(1) : undefined,",
+      "    'regexp': /x/,",
+      "    'set': root.Set ? new root.Set : undefined,",
+      "    'string': Object('a'),",
+      "    'symbol': root.Symbol ? root.Symbol() : undefined,",
+      "    'undefined': undefined,",
+      "    'weakMap': root.WeakMap ? new root.WeakMap : undefined,",
+      "    'weakSet': root.WeakSet ? new root.WeakSet : undefined",
+      '  };',
+      '',
+      "  ['" + arrayViews.join("', '") + "'].forEach(function(type) {",
+      '    var Ctor = root[type]',
+      '    object[type] = Ctor;',
+      '    object[type.toLowerCase()] = Ctor ? new Ctor(new ArrayBuffer(24)) : undefined;',
+      '  });',
+      '',
+      '  return object;',
+      '}())'
+    ].join('\n')));
+  });
+
+  // Add other realm values from an iframe.
+  lodashStable.attempt(function() {
+    _._realm = realm;
+
+    var iframe = document.createElement('iframe');
+    iframe.frameBorder = iframe.height = iframe.width = 0;
+    body.appendChild(iframe);
+
+    var idoc = (idoc = iframe.contentDocument || iframe.contentWindow).document || idoc;
+    idoc.write([
+      '<script>',
+      'var _ = parent._;',
+      '',
+      '  var noop = function() {},',
+      '      root = this;',
+      '',
+      'var object = {',
+      "  'ArrayBuffer': root.ArrayBuffer,",
+      "  'arguments': (function() { return arguments; }(1, 2, 3)),",
+      "  'array': [1],",
+      "  'arrayBuffer': root.ArrayBuffer ? new root.ArrayBuffer : undefined,",
+      "  'boolean': Object(false),",
+      "  'date': new Date,",
+      "  'errors': [new Error, new EvalError, new RangeError, new ReferenceError, new SyntaxError, new TypeError, new URIError],",
+      "  'function': noop,",
+      "  'map': root.Map ? new root.Map : undefined,",
+      "  'nan': NaN,",
+      "  'null': null,",
+      "  'number': Object(0),",
+      "  'object': { 'a': 1 },",
+      "  'promise': root.Promise ? Promise.resolve(1) : undefined,",
+      "  'regexp': /x/,",
+      "  'set': root.Set ? new root.Set : undefined,",
+      "  'string': Object('a'),",
+      "  'symbol': root.Symbol ? root.Symbol() : undefined,",
+      "  'undefined': undefined,",
+      "  'weakMap': root.WeakMap ? new root.WeakMap : undefined,",
+      "  'weakSet': root.WeakSet ? new root.WeakSet : undefined",
+      '};',
+      '',
+      "_.each(['" + arrayViews.join("', '") + "'], function(type) {",
+      '  var Ctor = root[type];',
+      '  object[type] = Ctor;',
+      '  object[type.toLowerCase()] = Ctor ? new Ctor(new ArrayBuffer(24)) : undefined;',
+      '});',
+      '',
+      '_.assign(_._realm, object);',
+      '<\/script>'
+    ].join('\n'));
+
+    idoc.close();
+    delete _._realm;
+  });
+
+  // Add a web worker.
+  lodashStable.attempt(function() {
+    var worker = new Worker('./asset/worker.js?t=' + (+new Date));
+    worker.addEventListener('message', function(e) {
+      _._VERSION = e.data || '';
+    }, false);
+
+    worker.postMessage(ui.buildPath);
+  });
+
+  // Expose internal modules for better code coverage.
+  lodashStable.attempt(function() {
+    var path = require('path'),
+        basePath = path.dirname(filePath);
+
+    if (isModularize && !(amd || isNpm)) {
+      lodashStable.each([
+        '_baseEach',
+        '_isIndex',
+        '_isIterateeCall'
+      ], function(relPath) {
+        var func = require(path.join(basePath, relPath)),
+            funcName = path.basename(relPath);
+
+        _['_' + funcName] = func[funcName] || func['default'] || func;
+      });
+    }
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  if (params) {
+    console.log('Running lodash tests.');
+    console.log('test.js invoked with arguments: ' + JSON.stringify(slice.call(params)));
+  }
+
+  QUnit.module(basename);
+
+  (function() {
+    QUnit.test('should support loading ' + basename + ' as the "lodash" module', function(assert) {
+      assert.expect(1);
+
+      if (amd) {
+        assert.strictEqual((lodashModule || {}).moduleName, 'lodash');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should support loading ' + basename + ' with the Require.js "shim" configuration option', function(assert) {
+      assert.expect(1);
+
+      if (amd && lodashStable.includes(ui.loaderPath, 'requirejs')) {
+        assert.strictEqual((shimmedModule || {}).moduleName, 'shimmed');
+      } else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should support loading ' + basename + ' as the "underscore" module', function(assert) {
+      assert.expect(1);
+
+      if (amd) {
+        assert.strictEqual((underscoreModule || {}).moduleName, 'underscore');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should support loading ' + basename + ' in a web worker', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      if (Worker) {
+        var limit = 30000 / QUnit.config.asyncRetries,
+            start = +new Date;
+
+        var attempt = function() {
+          var actual = _._VERSION;
+          if ((new Date - start) < limit && typeof actual != 'string') {
+            setTimeout(attempt, 16);
+            return;
+          }
+          assert.strictEqual(actual, _.VERSION);
+          done();
+        };
+
+        attempt();
+      }
+      else {
+        skipAssert(assert);
+        done();
+      }
+    });
+
+    QUnit.test('should not add `Function.prototype` extensions to lodash', function(assert) {
+      assert.expect(1);
+
+      if (lodashBizarro) {
+        assert.notOk('_method' in lodashBizarro);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should avoid non-native built-ins', function(assert) {
+      assert.expect(7);
+
+      function message(lodashMethod, nativeMethod) {
+        return '`' + lodashMethod + '` should avoid overwritten native `' + nativeMethod + '`';
+      }
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var object = { 'a': 1 },
+          otherObject = { 'b': 2 },
+          largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(object));
+
+      if (lodashBizarro) {
+        try {
+          var actual = lodashBizarro.keysIn(new Foo).sort();
+        } catch (e) {
+          actual = null;
+        }
+        var label = message('_.keysIn', 'Object#propertyIsEnumerable');
+        assert.deepEqual(actual, ['a', 'b'], label);
+
+        try {
+          var actual = lodashBizarro.isEmpty({});
+        } catch (e) {
+          actual = null;
+        }
+        var label = message('_.isEmpty', 'Object#propertyIsEnumerable');
+        assert.strictEqual(actual, true, label);
+
+        try {
+          actual = [
+            lodashBizarro.difference([object, otherObject], largeArray),
+            lodashBizarro.intersection(largeArray, [object]),
+            lodashBizarro.uniq(largeArray)
+          ];
+        } catch (e) {
+          actual = null;
+        }
+        label = message('_.difference`, `_.intersection`, and `_.uniq', 'Object.create` and `Map');
+        assert.deepEqual(actual, [[otherObject], [object], [object]], label);
+
+        try {
+          if (Symbol) {
+            object[symbol] = {};
+          }
+          actual = [
+            lodashBizarro.clone(object),
+            lodashBizarro.cloneDeep(object)
+          ];
+        } catch (e) {
+          actual = null;
+        }
+        label = message('_.clone` and `_.cloneDeep', 'Object.getOwnPropertySymbols');
+        assert.deepEqual(actual, [object, object], label);
+
+        try {
+          var symObject = Object(symbol);
+
+          // Avoid symbol detection in Babel's `typeof` helper.
+          symObject.constructor = Object;
+
+          actual = [
+            Symbol ? lodashBizarro.clone(symObject) : { 'constructor': Object },
+            Symbol ? lodashBizarro.isEqual(symObject, Object(symbol)) : false,
+            Symbol ? lodashBizarro.toString(symObject) : ''
+          ];
+        } catch (e) {
+          actual = null;
+        }
+        label = message('_.clone`, `_.isEqual`, and `_.toString', 'Symbol');
+        assert.deepEqual(actual, [{ 'constructor': Object }, false, ''], label);
+
+        try {
+          var map = new lodashBizarro.memoize.Cache;
+          actual = map.set('a', 1).get('a');
+        } catch (e) {
+          actual = null;
+        }
+        label = message('_.memoize.Cache', 'Map');
+        assert.deepEqual(actual, 1, label);
+
+        try {
+          map = new (Map || Object);
+          if (Symbol && Symbol.iterator) {
+            map[Symbol.iterator] = null;
+          }
+          actual = lodashBizarro.toArray(map);
+        } catch (e) {
+          actual = null;
+        }
+        label = message('_.toArray', 'Map');
+        assert.deepEqual(actual, [], label);
+      }
+      else {
+        skipAssert(assert, 7);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('isIndex');
+
+  (function() {
+    var func = _._isIndex;
+
+    QUnit.test('should return `true` for indexes', function(assert) {
+      assert.expect(1);
+
+      if (func) {
+        var values = [[0], ['0'], ['1'], [3, 4], [MAX_SAFE_INTEGER - 1]],
+            expected = lodashStable.map(values, alwaysTrue);
+
+        var actual = lodashStable.map(values, function(args) {
+          return func.apply(undefined, args);
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non-indexes', function(assert) {
+      assert.expect(1);
+
+      if (func) {
+        var values = [['1abc'], ['07'], ['0001'], [-1], [3, 3], [1.1], [MAX_SAFE_INTEGER]],
+            expected = lodashStable.map(values, alwaysFalse);
+
+        var actual = lodashStable.map(values, function(args) {
+          return func.apply(undefined, args);
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('isIterateeCall');
+
+  (function() {
+    var array = [1],
+        func = _._isIterateeCall,
+        object =  { 'a': 1 };
+
+    QUnit.test('should return `true` for iteratee calls', function(assert) {
+      assert.expect(3);
+
+      function Foo() {}
+      Foo.prototype.a = 1;
+
+      if (func) {
+        assert.strictEqual(func(1, 0, array), true);
+        assert.strictEqual(func(1, 'a', object), true);
+        assert.strictEqual(func(1, 'a', new Foo), true);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should return `false` for non-iteratee calls', function(assert) {
+      assert.expect(4);
+
+      if (func) {
+        assert.strictEqual(func(2, 0, array), false);
+        assert.strictEqual(func(1, 1.1, array), false);
+        assert.strictEqual(func(1, 0, { 'length': MAX_SAFE_INTEGER + 1 }), false);
+        assert.strictEqual(func(1, 'b', object), false);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should work with `NaN` values', function(assert) {
+      assert.expect(2);
+
+      if (func) {
+        assert.strictEqual(func(NaN, 0, [NaN]), true);
+        assert.strictEqual(func(NaN, 'a', { 'a': NaN }), true);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should not error when `index` is an object without a `toString` method', function(assert) {
+      assert.expect(1);
+
+      if (func) {
+        try {
+          var actual = func(1, { 'toString': null }, [1]);
+        } catch (e) {
+          var message = e.message;
+        }
+        assert.strictEqual(actual, false, message || '');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash constructor');
+
+  (function() {
+    var values = empties.concat(true, 1, 'a'),
+        expected = lodashStable.map(values, alwaysTrue);
+
+    QUnit.test('should create a new instance when called without the `new` operator', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = lodashStable.map(values, function(value) {
+          return _(value) instanceof _;
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return the given `lodash` instances', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = lodashStable.map(values, function(value) {
+          var wrapped = _(value);
+          return _(wrapped) === wrapped;
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should convert foreign wrapped values to `lodash` instances', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm && lodashBizarro) {
+        var actual = lodashStable.map(values, function(value) {
+          var wrapped = _(lodashBizarro(value)),
+              unwrapped = wrapped.value();
+
+          return wrapped instanceof _ &&
+            ((unwrapped === value) || (unwrapped !== unwrapped && value !== value));
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.add');
+
+  (function() {
+    QUnit.test('should add two numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.add(6, 4), 10);
+      assert.strictEqual(_.add(-6, 4), -2);
+      assert.strictEqual(_.add(-6, -4), -10);
+    });
+
+    QUnit.test('should not coerce arguments to numbers', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.add('6', '4'), '64');
+      assert.strictEqual(_.add('x', 'y'), 'xy');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.after');
+
+  (function() {
+    function after(n, times) {
+      var count = 0;
+      lodashStable.times(times, _.after(n, function() { count++; }));
+      return count;
+    }
+
+    QUnit.test('should create a function that invokes `func` after `n` calls', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(after(5, 5), 1, 'after(n) should invoke `func` after being called `n` times');
+      assert.strictEqual(after(5, 4), 0, 'after(n) should not invoke `func` before being called `n` times');
+      assert.strictEqual(after(0, 0), 0, 'after(0) should not invoke `func` immediately');
+      assert.strictEqual(after(0, 1), 1, 'after(0) should invoke `func` when called once');
+    });
+
+    QUnit.test('should coerce `n` values of `NaN` to `0`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(after(NaN, 1), 1);
+    });
+
+    QUnit.test('should not set a `this` binding', function(assert) {
+      assert.expect(2);
+
+      var after = _.after(1, function(assert) { return ++this.count; }),
+          object = { 'after': after, 'count': 0 };
+
+      object.after();
+      assert.strictEqual(object.after(), 2);
+      assert.strictEqual(object.count, 2);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.ary');
+
+  (function() {
+    function fn(a, b, c) {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should cap the number of arguments provided to `func`', function(assert) {
+      assert.expect(2);
+
+      var actual = lodashStable.map(['6', '8', '10'], _.ary(parseInt, 1));
+      assert.deepEqual(actual, [6, 8, 10]);
+
+      var capped = _.ary(fn, 2);
+      assert.deepEqual(capped('a', 'b', 'c', 'd'), ['a', 'b']);
+    });
+
+    QUnit.test('should use `func.length` if `n` is not given', function(assert) {
+      assert.expect(1);
+
+      var capped = _.ary(fn);
+      assert.deepEqual(capped('a', 'b', 'c', 'd'), ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should treat a negative `n` as `0`', function(assert) {
+      assert.expect(1);
+
+      var capped = _.ary(fn, -1);
+
+      try {
+        var actual = capped('a');
+      } catch (e) {}
+
+      assert.deepEqual(actual, []);
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(1);
+
+      var values = ['1', 1.6, 'xyz'],
+          expected = [['a'], ['a'], []];
+
+      var actual = lodashStable.map(values, function(n) {
+        var capped = _.ary(fn, n);
+        return capped('a', 'b');
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work when given less than the capped number of arguments', function(assert) {
+      assert.expect(1);
+
+      var capped = _.ary(fn, 3);
+      assert.deepEqual(capped('a'), ['a']);
+    });
+
+    QUnit.test('should use the existing `ary` if smaller', function(assert) {
+      assert.expect(1);
+
+      var capped = _.ary(_.ary(fn, 1), 2);
+      assert.deepEqual(capped('a', 'b', 'c'), ['a']);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var funcs = lodashStable.map([fn], _.ary),
+          actual = funcs[0]('a', 'b', 'c');
+
+      assert.deepEqual(actual, ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should work when combined with other methods that use metadata', function(assert) {
+      assert.expect(2);
+
+      var array = ['a', 'b', 'c'],
+          includes = _.curry(_.rearg(_.ary(_.includes, 2), 1, 0), 2);
+
+      assert.strictEqual(includes('b')(array, 2), true);
+
+      if (!isNpm) {
+        includes = _(_.includes).ary(2).rearg(1, 0).curry(2).value();
+        assert.strictEqual(includes('b')(array, 2), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.assignIn');
+
+  (function() {
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.extend, _.assignIn);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.assign and lodash.assignIn');
+
+  lodashStable.each(['assign', 'assignIn'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should assign source properties to `object`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func({ 'a': 1 }, { 'b': 2 }), { 'a': 1, 'b': 2 });
+    });
+
+    QUnit.test('`_.' + methodName + '` should accept multiple sources', function(assert) {
+      assert.expect(2);
+
+      var expected = { 'a': 1, 'b': 2, 'c': 3 };
+      assert.deepEqual(func({ 'a': 1 }, { 'b': 2 }, { 'c': 3 }), expected);
+      assert.deepEqual(func({ 'a': 1 }, { 'b': 2, 'c': 2 }, { 'c': 3 }), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should overwrite destination properties', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'a': 3, 'b': 2, 'c': 1 };
+      assert.deepEqual(func({ 'a': 1, 'b': 2 }, expected), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should assign source properties with nullish values', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'a': null, 'b': undefined, 'c': null };
+      assert.deepEqual(func({ 'a': 1, 'b': 2 }, expected), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should skip assignments if values are the same', function(assert) {
+      assert.expect(1);
+
+      var object = {};
+
+      var descriptor = {
+        'configurable': true,
+        'enumerable': true,
+        'set': function() { throw new Error; }
+      };
+
+      var source = {
+        'a': 1,
+        'b': undefined,
+        'c': NaN,
+        'd': undefined,
+        'constructor': Object,
+        'toString': lodashStable.constant('source')
+      };
+
+      defineProperty(object, 'a', lodashStable.assign({}, descriptor, {
+        'get': alwaysOne
+      }));
+
+      defineProperty(object, 'b', lodashStable.assign({}, descriptor, {
+        'get': noop
+      }));
+
+      defineProperty(object, 'c', lodashStable.assign({}, descriptor, {
+        'get': alwaysNaN
+      }));
+
+      defineProperty(object, 'constructor', lodashStable.assign({}, descriptor, {
+        'get': lodashStable.constant(Object)
+      }));
+
+      try {
+        var actual = func(object, source);
+      } catch (e) {}
+
+      assert.deepEqual(actual, source);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat sparse array sources as dense', function(assert) {
+      assert.expect(1);
+
+      var array = [1];
+      array[2] = 3;
+
+      assert.deepEqual(func({}, array), { '0': 1, '1': undefined, '2': 3 });
+    });
+
+    QUnit.test('`_.' + methodName + '` should assign values of prototype objects', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype.a = 1;
+
+      assert.deepEqual(func({}, Foo.prototype), { 'a': 1 });
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce string sources to objects', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func({}, 'a'), { '0': 'a' });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.assignInWith');
+
+  (function() {
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.extendWith, _.assignInWith);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.assignWith and lodash.assignInWith');
+
+  lodashStable.each(['assignWith', 'assignInWith'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should work with a `customizer` callback', function(assert) {
+      assert.expect(1);
+
+      var actual = func({ 'a': 1, 'b': 2 }, { 'a': 3, 'c': 3 }, function(a, b) {
+        return a === undefined ? b : a;
+      });
+
+      assert.deepEqual(actual, { 'a': 1, 'b': 2, 'c': 3 });
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a `customizer` that returns `undefined`', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'a': undefined };
+      assert.deepEqual(func({}, expected, noop), expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.at');
+
+  (function() {
+    var args = arguments,
+        array = ['a', 'b', 'c'],
+        object = { 'a': [{ 'b': { 'c': 3 } }, 4] };
+
+    QUnit.test('should return the elements corresponding to the specified keys', function(assert) {
+      assert.expect(1);
+
+      var actual = _.at(array, [0, 2]);
+      assert.deepEqual(actual, ['a', 'c']);
+    });
+
+    QUnit.test('should return `undefined` for nonexistent keys', function(assert) {
+      assert.expect(1);
+
+      var actual = _.at(array, [2, 4, 0]);
+      assert.deepEqual(actual, ['c', undefined, 'a']);
+    });
+
+    QUnit.test('should work with non-index keys on array values', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.reject(empties, function(value) {
+        return (value === 0) || lodashStable.isArray(value);
+      }).concat(-1, 1.1);
+
+      var array = lodashStable.transform(values, function(result, value) {
+        result[value] = 1;
+      }, []);
+
+      var expected = lodashStable.map(values, alwaysOne),
+          actual = _.at(array, values);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an empty array when no keys are given', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.at(array), []);
+      assert.deepEqual(_.at(array, [], []), []);
+    });
+
+    QUnit.test('should accept multiple key arguments', function(assert) {
+      assert.expect(1);
+
+      var actual = _.at(['a', 'b', 'c', 'd'], 3, 0, 2);
+      assert.deepEqual(actual, ['d', 'a', 'c']);
+    });
+
+    QUnit.test('should work with a falsey `object` argument when keys are given', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, lodashStable.constant(Array(4)));
+
+      var actual = lodashStable.map(falsey, function(object) {
+        try {
+          return _.at(object, 0, 1, 'pop', 'push');
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with an `arguments` object for `object`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.at(args, [2, 0]);
+      assert.deepEqual(actual, [3, 1]);
+    });
+
+    QUnit.test('should work with `arguments` object as secondary arguments', function(assert) {
+      assert.expect(1);
+
+      var actual = _.at([1, 2, 3, 4, 5], args);
+      assert.deepEqual(actual, [2, 3, 4]);
+    });
+
+    QUnit.test('should work with an object for `object`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.at(object, ['a[0].b.c', 'a[1]']);
+      assert.deepEqual(actual, [3, 4]);
+    });
+
+    QUnit.test('should pluck inherited property values', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var actual = _.at(new Foo, 'b');
+      assert.deepEqual(actual, [2]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var largeArray = lodashStable.range(LARGE_ARRAY_SIZE),
+            smallArray = array;
+
+        lodashStable.each([[2], ['2'], [2, 1]], function(paths) {
+          lodashStable.times(2, function(index) {
+            var array = index ? largeArray : smallArray,
+                wrapped = _(array).map(identity).at(paths);
+
+            assert.deepEqual(wrapped.value(), _.at(_.map(array, identity), paths));
+          });
+        });
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+
+    QUnit.test('should support shortcut fusion', function(assert) {
+      assert.expect(8);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            count = 0,
+            iteratee = function(value) { count++; return square(value); },
+            lastIndex = LARGE_ARRAY_SIZE - 1;
+
+        lodashStable.each([lastIndex, lastIndex + '', LARGE_ARRAY_SIZE, []], function(n, index) {
+          count = 0;
+          var actual = _(array).map(iteratee).at(n).value(),
+              expected = index < 2 ? 1 : 0;
+
+          assert.strictEqual(count, expected);
+
+          expected = index == 3 ? [] : [index == 2 ? undefined : square(lastIndex)];
+          assert.deepEqual(actual, expected);
+        });
+      }
+      else {
+        skipAssert(assert, 8);
+      }
+    });
+
+    QUnit.test('work with an object for `object` when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var paths = ['a[0].b.c', 'a[1]'],
+            actual = _(object).map(identity).at(paths).value();
+
+        assert.deepEqual(actual, _.at(_.map(object, identity), paths));
+
+        var indexObject = { '0': 1 };
+        actual = _(indexObject).at(0).value();
+        assert.deepEqual(actual, _.at(indexObject, 0));
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.attempt');
+
+  (function() {
+    QUnit.test('should return the result of `func`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.attempt(lodashStable.constant('x')), 'x');
+    });
+
+    QUnit.test('should provide additional arguments to `func`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.attempt(function() { return slice.call(arguments); }, 1, 2);
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('should return the caught error', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(errors, alwaysTrue);
+
+      var actual = lodashStable.map(errors, function(error) {
+        return _.attempt(function() { throw error; }) === error;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce errors to error objects', function(assert) {
+      assert.expect(1);
+
+      var actual = _.attempt(function() { throw 'x'; });
+      assert.ok(lodashStable.isEqual(actual, Error('x')));
+    });
+
+    QUnit.test('should preserve custom errors', function(assert) {
+      assert.expect(1);
+
+      var actual = _.attempt(function() { throw new CustomError('x'); });
+      assert.ok(actual instanceof CustomError);
+    });
+
+    QUnit.test('should work with an error object from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.errors) {
+        var expected = lodashStable.map(realm.errors, alwaysTrue);
+
+        var actual = lodashStable.map(realm.errors, function(error) {
+          return _.attempt(function() { throw error; }) === error;
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_(lodashStable.constant('x')).attempt(), 'x');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(lodashStable.constant('x')).chain().attempt() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.before');
+
+  (function() {
+    function before(n, times) {
+      var count = 0;
+      lodashStable.times(times, _.before(n, function() { count++; }));
+      return count;
+    }
+
+    QUnit.test('should create a function that invokes `func` after `n` calls', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(before(5, 4), 4, 'before(n) should invoke `func` before being called `n` times');
+      assert.strictEqual(before(5, 6), 4, 'before(n) should not invoke `func` after being called `n - 1` times');
+      assert.strictEqual(before(0, 0), 0, 'before(0) should not invoke `func` immediately');
+      assert.strictEqual(before(0, 1), 0, 'before(0) should not invoke `func` when called');
+    });
+
+    QUnit.test('should coerce `n` values of `NaN` to `0`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(before(NaN, 1), 0);
+    });
+
+    QUnit.test('should not set a `this` binding', function(assert) {
+      assert.expect(2);
+
+      var before = _.before(2, function(assert) { return ++this.count; }),
+          object = { 'before': before, 'count': 0 };
+
+      object.before();
+      assert.strictEqual(object.before(), 1);
+      assert.strictEqual(object.count, 1);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.bind');
+
+  (function() {
+    function fn() {
+      var result = [this];
+      push.apply(result, arguments);
+      return result;
+    }
+
+    QUnit.test('should bind a function to an object', function(assert) {
+      assert.expect(1);
+
+      var object = {},
+          bound = _.bind(fn, object);
+
+      assert.deepEqual(bound('a'), [object, 'a']);
+    });
+
+    QUnit.test('should accept a falsey `thisArg` argument', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.reject(falsey.slice(1), function(value) { return value == null; }),
+          expected = lodashStable.map(values, function(value) { return [value]; });
+
+      var actual = lodashStable.map(values, function(value) {
+        try {
+          var bound = _.bind(fn, value);
+          return bound();
+        } catch (e) {}
+      });
+
+      assert.ok(lodashStable.every(actual, function(value, index) {
+        return lodashStable.isEqual(value, expected[index]);
+      }));
+    });
+
+    QUnit.test('should bind a function to nullish values', function(assert) {
+      assert.expect(6);
+
+      var bound = _.bind(fn, null),
+          actual = bound('a');
+
+      assert.ok((actual[0] === null) || (actual[0] && actual[0].Array));
+      assert.strictEqual(actual[1], 'a');
+
+      lodashStable.times(2, function(index) {
+        bound = index ? _.bind(fn, undefined) : _.bind(fn);
+        actual = bound('b');
+
+        assert.ok((actual[0] === undefined) || (actual[0] && actual[0].Array));
+        assert.strictEqual(actual[1], 'b');
+      });
+    });
+
+    QUnit.test('should partially apply arguments ', function(assert) {
+      assert.expect(4);
+
+      var object = {},
+          bound = _.bind(fn, object, 'a');
+
+      assert.deepEqual(bound(), [object, 'a']);
+
+      bound = _.bind(fn, object, 'a');
+      assert.deepEqual(bound('b'), [object, 'a', 'b']);
+
+      bound = _.bind(fn, object, 'a', 'b');
+      assert.deepEqual(bound(), [object, 'a', 'b']);
+      assert.deepEqual(bound('c', 'd'), [object, 'a', 'b', 'c', 'd']);
+    });
+
+    QUnit.test('should support placeholders', function(assert) {
+      assert.expect(4);
+
+      var object = {},
+          ph = _.bind.placeholder,
+          bound = _.bind(fn, object, ph, 'b', ph);
+
+      assert.deepEqual(bound('a', 'c'), [object, 'a', 'b', 'c']);
+      assert.deepEqual(bound('a'), [object, 'a', 'b', undefined]);
+      assert.deepEqual(bound('a', 'c', 'd'), [object, 'a', 'b', 'c', 'd']);
+      assert.deepEqual(bound(), [object, undefined, 'b', undefined]);
+    });
+
+    QUnit.test('should use `_.placeholder` when set', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var _ph = _.placeholder = {},
+            ph = _.bind.placeholder,
+            object = {},
+            bound = _.bind(fn, object, _ph, 'b', ph);
+
+        assert.deepEqual(bound('a', 'c'), [object, 'a', 'b', ph, 'c']);
+        delete _.placeholder;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should create a function with a `length` of `0`', function(assert) {
+      assert.expect(2);
+
+      var fn = function(a, b, c) {},
+          bound = _.bind(fn, {});
+
+      assert.strictEqual(bound.length, 0);
+
+      bound = _.bind(fn, {}, 1);
+      assert.strictEqual(bound.length, 0);
+    });
+
+    QUnit.test('should ignore binding when called with the `new` operator', function(assert) {
+      assert.expect(3);
+
+      function Foo() {
+        return this;
+      }
+
+      var bound = _.bind(Foo, { 'a': 1 }),
+          newBound = new bound;
+
+      assert.strictEqual(bound().a, 1);
+      assert.strictEqual(newBound.a, undefined);
+      assert.ok(newBound instanceof Foo);
+    });
+
+    QUnit.test('should handle a number of arguments when called with the `new` operator', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        return this;
+      }
+
+      function Bar() {}
+
+      var thisArg = { 'a': 1 },
+          boundFoo = _.bind(Foo, thisArg),
+          boundBar = _.bind(Bar, thisArg),
+          count = 9,
+          expected = lodashStable.times(count, lodashStable.constant([undefined, undefined]));
+
+      var actual = lodashStable.times(count, function(index) {
+        try {
+          switch (index) {
+            case 0: return [new boundFoo().a, new boundBar().a];
+            case 1: return [new boundFoo(1).a, new boundBar(1).a];
+            case 2: return [new boundFoo(1, 2).a, new boundBar(1, 2).a];
+            case 3: return [new boundFoo(1, 2, 3).a, new boundBar(1, 2, 3).a];
+            case 4: return [new boundFoo(1, 2, 3, 4).a, new boundBar(1, 2, 3, 4).a];
+            case 5: return [new boundFoo(1, 2, 3, 4, 5).a, new boundBar(1, 2, 3, 4, 5).a];
+            case 6: return [new boundFoo(1, 2, 3, 4, 5, 6).a, new boundBar(1, 2, 3, 4, 5, 6).a];
+            case 7: return [new boundFoo(1, 2, 3, 4, 5, 6, 7).a, new boundBar(1, 2, 3, 4, 5, 6, 7).a];
+            case 8: return [new boundFoo(1, 2, 3, 4, 5, 6, 7, 8).a, new boundBar(1, 2, 3, 4, 5, 6, 7, 8).a];
+          }
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should ensure `new bound` is an instance of `func`', function(assert) {
+      assert.expect(2);
+
+      function Foo(value) {
+        return value && object;
+      }
+
+      var bound = _.bind(Foo),
+          object = {};
+
+      assert.ok(new bound instanceof Foo);
+      assert.strictEqual(new bound(true), object);
+    });
+
+    QUnit.test('should append array arguments to partially applied arguments', function(assert) {
+      assert.expect(1);
+
+      var object = {},
+          bound = _.bind(fn, object, 'a');
+
+      assert.deepEqual(bound(['b'], 'c'), [object, 'a', ['b'], 'c']);
+    });
+
+    QUnit.test('should not rebind functions', function(assert) {
+      assert.expect(3);
+
+      var object1 = {},
+          object2 = {},
+          object3 = {};
+
+      var bound1 = _.bind(fn, object1),
+          bound2 = _.bind(bound1, object2, 'a'),
+          bound3 = _.bind(bound1, object3, 'b');
+
+      assert.deepEqual(bound1(), [object1]);
+      assert.deepEqual(bound2(), [object1, 'a']);
+      assert.deepEqual(bound3(), [object1, 'b']);
+    });
+
+    QUnit.test('should not error when instantiating bound built-ins', function(assert) {
+      assert.expect(2);
+
+      var Ctor = _.bind(Date, null),
+          expected = new Date(2012, 4, 23, 0, 0, 0, 0);
+
+      try {
+        var actual = new Ctor(2012, 4, 23, 0, 0, 0, 0);
+      } catch (e) {}
+
+      assert.deepEqual(actual, expected);
+
+      Ctor = _.bind(Date, null, 2012, 4, 23);
+
+      try {
+        actual = new Ctor(0, 0, 0, 0);
+      } catch (e) {}
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not error when calling bound class constructors with the `new` operator', function(assert) {
+      assert.expect(1);
+
+      var createCtor = lodashStable.attempt(Function, '"use strict";return class A{}');
+
+      if (typeof createCtor == 'function') {
+        var bound = _.bind(createCtor()),
+            count = 8,
+            expected = lodashStable.times(count, alwaysTrue);
+
+        var actual = lodashStable.times(count, function(index) {
+          try {
+            switch (index) {
+              case 0: return !!(new bound);
+              case 1: return !!(new bound(1));
+              case 2: return !!(new bound(1, 2));
+              case 3: return !!(new bound(1, 2, 3));
+              case 4: return !!(new bound(1, 2, 3, 4));
+              case 5: return !!(new bound(1, 2, 3, 4, 5));
+              case 6: return !!(new bound(1, 2, 3, 4, 5, 6));
+              case 7: return !!(new bound(1, 2, 3, 4, 5, 6, 7));
+            }
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var object = {},
+            bound = _(fn).bind({}, 'a', 'b');
+
+        assert.ok(bound instanceof _);
+
+        var actual = bound.value()('c');
+        assert.deepEqual(actual, [object, 'a', 'b', 'c']);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.bindAll');
+
+  (function() {
+    var args = arguments;
+
+    var source = {
+      '_n0': -2,
+      '_p0': -1,
+      '_a': 1,
+      '_b': 2,
+      '_c': 3,
+      '_d': 4,
+      '-0': function() { return this._n0; },
+      '0': function() { return this._p0; },
+      'a': function() { return this._a; },
+      'b': function() { return this._b; },
+      'c': function() { return this._c; },
+      'd': function() { return this._d; }
+    };
+
+    QUnit.test('should accept individual method names', function(assert) {
+      assert.expect(1);
+
+      var object = lodashStable.cloneDeep(source);
+      _.bindAll(object, 'a', 'b');
+
+      var actual = lodashStable.map(['a', 'b', 'c'], function(key) {
+        return object[key].call({});
+      });
+
+      assert.deepEqual(actual, [1, 2, undefined]);
+    });
+
+    QUnit.test('should accept arrays of method names', function(assert) {
+      assert.expect(1);
+
+      var object = lodashStable.cloneDeep(source);
+      _.bindAll(object, ['a', 'b'], ['c']);
+
+      var actual = lodashStable.map(['a', 'b', 'c', 'd'], function(key) {
+        return object[key].call({});
+      });
+
+      assert.deepEqual(actual, [1, 2, 3, undefined]);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var props = [-0, Object(-0), 0, Object(0)];
+
+      var actual = lodashStable.map(props, function(key) {
+        var object = lodashStable.cloneDeep(source);
+        _.bindAll(object, key);
+        return object[lodashStable.toString(key)].call({});
+      });
+
+      assert.deepEqual(actual, [-2, -2, -1, -1]);
+    });
+
+    QUnit.test('should work with an array `object` argument', function(assert) {
+      assert.expect(1);
+
+      var array = ['push', 'pop'];
+      _.bindAll(array);
+      assert.strictEqual(array.pop, arrayProto.pop);
+    });
+
+    QUnit.test('should work with `arguments` objects as secondary arguments', function(assert) {
+      assert.expect(1);
+
+      var object = lodashStable.cloneDeep(source);
+      _.bindAll(object, args);
+
+      var actual = lodashStable.map(args, function(key) {
+        return object[key].call({});
+      });
+
+      assert.deepEqual(actual, [1]);
+    });
+  }('a'));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.bindKey');
+
+  (function() {
+    QUnit.test('should work when the target function is overwritten', function(assert) {
+      assert.expect(2);
+
+      var object = {
+        'user': 'fred',
+        'greet': function(greeting) {
+          return this.user + ' says: ' + greeting;
+        }
+      };
+
+      var bound = _.bindKey(object, 'greet', 'hi');
+      assert.strictEqual(bound(), 'fred says: hi');
+
+      object.greet = function(greeting) {
+        return this.user + ' says: ' + greeting + '!';
+      };
+
+      assert.strictEqual(bound(), 'fred says: hi!');
+    });
+
+    QUnit.test('should support placeholders', function(assert) {
+      assert.expect(4);
+
+      var object = {
+        'fn': function() {
+          return slice.call(arguments);
+        }
+      };
+
+      var ph = _.bindKey.placeholder,
+          bound = _.bindKey(object, 'fn', ph, 'b', ph);
+
+      assert.deepEqual(bound('a', 'c'), ['a', 'b', 'c']);
+      assert.deepEqual(bound('a'), ['a', 'b', undefined]);
+      assert.deepEqual(bound('a', 'c', 'd'), ['a', 'b', 'c', 'd']);
+      assert.deepEqual(bound(), [undefined, 'b', undefined]);
+    });
+
+    QUnit.test('should use `_.placeholder` when set', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var object = {
+          'fn': function() {
+            return slice.call(arguments);
+          }
+        };
+
+        var _ph = _.placeholder = {},
+            ph = _.bindKey.placeholder,
+            bound = _.bindKey(object, 'fn', _ph, 'b', ph);
+
+        assert.deepEqual(bound('a', 'c'), ['a', 'b', ph, 'c']);
+        delete _.placeholder;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should ensure `new bound` is an instance of `object[key]`', function(assert) {
+      assert.expect(2);
+
+      function Foo(value) {
+        return value && object;
+      }
+
+      var object = { 'Foo': Foo },
+          bound = _.bindKey(object, 'Foo');
+
+      assert.ok(new bound instanceof Foo);
+      assert.strictEqual(new bound(true), object);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('case methods');
+
+  lodashStable.each(['camel', 'kebab', 'lower', 'snake', 'start', 'upper'], function(caseName) {
+    var methodName = caseName + 'Case',
+        func = _[methodName];
+
+    var strings = [
+      'foo bar', 'Foo bar', 'foo Bar', 'Foo Bar',
+      'FOO BAR', 'fooBar', '--foo-bar--', '__foo_bar__'
+    ];
+
+    var converted = (function() {
+      switch (caseName) {
+        case 'camel': return 'fooBar';
+        case 'kebab': return 'foo-bar';
+        case 'lower': return 'foo bar';
+        case 'snake': return 'foo_bar';
+        case 'start': return 'Foo Bar';
+        case 'upper': return 'FOO BAR';
+      }
+    }());
+
+    QUnit.test('`_.' + methodName + '` should convert `string` to ' + caseName + ' case', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(strings, function(string) {
+        var expected = (caseName == 'start' && string == 'FOO BAR') ? string : converted;
+        return func(string) === expected;
+      });
+
+      assert.deepEqual(actual, lodashStable.map(strings, alwaysTrue));
+    });
+
+    QUnit.test('`_.' + methodName + '` should handle double-converting strings', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(strings, function(string) {
+        var expected = (caseName == 'start' && string == 'FOO BAR') ? string : converted;
+        return func(func(string)) === expected;
+      });
+
+      assert.deepEqual(actual, lodashStable.map(strings, alwaysTrue));
+    });
+
+    QUnit.test('`_.' + methodName + '` should deburr letters', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(burredLetters, function(burred, index) {
+        var letter = deburredLetters[index];
+        if (caseName == 'start') {
+          letter = lodashStable.capitalize(letter);
+        } else if (caseName == 'upper') {
+          letter = letter.toUpperCase();
+        } else {
+          letter = letter.toLowerCase();
+        }
+        return func(burred) === letter;
+      });
+
+      assert.deepEqual(actual, lodashStable.map(burredLetters, alwaysTrue));
+    });
+
+    QUnit.test('`_.' + methodName + '` should remove contraction apostrophes', function(assert) {
+      assert.expect(2);
+
+      var postfixes = ['d', 'll', 'm', 're', 's', 't', 've'];
+
+      lodashStable.each(["'", '\u2019'], function(apos) {
+        var actual = lodashStable.map(postfixes, function(postfix) {
+          return func('a b' + apos + postfix +  ' c');
+        });
+
+        var expected = lodashStable.map(postfixes, function(postfix) {
+          switch (caseName) {
+            case 'camel': return 'aB'  + postfix + 'C';
+            case 'kebab': return 'a-b' + postfix + '-c';
+            case 'lower': return 'a b' + postfix + ' c';
+            case 'snake': return 'a_b' + postfix + '_c';
+            case 'start': return 'A B' + postfix + ' C';
+            case 'upper': return 'A B' + postfix.toUpperCase() + ' C';
+          }
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should remove latin-1 mathematical operators', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(['\xd7', '\xf7'], func);
+      assert.deepEqual(actual, ['', '']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `string` to a string', function(assert) {
+      assert.expect(2);
+
+      var string = 'foo bar';
+      assert.strictEqual(func(Object(string)), converted);
+      assert.strictEqual(func({ 'toString': lodashStable.constant(string) }), converted);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an unwrapped value implicitly when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_('foo bar')[methodName](), converted);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_('foo bar').chain()[methodName]() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  (function() {
+    QUnit.test('should get the original value after cycling through all case methods', function(assert) {
+      assert.expect(1);
+
+      var funcs = [_.camelCase, _.kebabCase, _.lowerCase, _.snakeCase, _.startCase, _.lowerCase, _.camelCase];
+
+      var actual = lodashStable.reduce(funcs, function(result, func) {
+        return func(result);
+      }, 'enable 6h format');
+
+      assert.strictEqual(actual, 'enable6HFormat');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.camelCase');
+
+  (function() {
+    QUnit.test('should work with numbers', function(assert) {
+      assert.expect(6);
+
+      assert.strictEqual(_.camelCase('12 feet'), '12Feet');
+      assert.strictEqual(_.camelCase('enable 6h format'), 'enable6HFormat');
+      assert.strictEqual(_.camelCase('enable 24H format'), 'enable24HFormat');
+      assert.strictEqual(_.camelCase('too legit 2 quit'), 'tooLegit2Quit');
+      assert.strictEqual(_.camelCase('walk 500 miles'), 'walk500Miles');
+      assert.strictEqual(_.camelCase('xhr2 request'), 'xhr2Request');
+    });
+
+    QUnit.test('should handle acronyms', function(assert) {
+      assert.expect(6);
+
+      lodashStable.each(['safe HTML', 'safeHTML'], function(string) {
+        assert.strictEqual(_.camelCase(string), 'safeHtml');
+      });
+
+      lodashStable.each(['escape HTML entities', 'escapeHTMLEntities'], function(string) {
+        assert.strictEqual(_.camelCase(string), 'escapeHtmlEntities');
+      });
+
+      lodashStable.each(['XMLHttpRequest', 'XmlHTTPRequest'], function(string) {
+        assert.strictEqual(_.camelCase(string), 'xmlHttpRequest');
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.capitalize');
+
+  (function() {
+    QUnit.test('should capitalize the first character of a string', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.capitalize('fred'), 'Fred');
+      assert.strictEqual(_.capitalize('Fred'), 'Fred');
+      assert.strictEqual(_.capitalize(' fred'), ' fred');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.castArray');
+
+  (function() {
+    QUnit.test('should wrap non-array items in an array', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat(true, 1, 'a', { 'a': 1 }),
+          expected = lodashStable.map(values, function(value) { return [value]; }),
+          actual = lodashStable.map(values, _.castArray);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return array values by reference', function(assert) {
+      assert.expect(1);
+
+      var array = [1];
+      assert.strictEqual(_.castArray(array), array);
+    });
+
+    QUnit.test('should return an empty array when no arguments are given', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.castArray(), []);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.chain');
+
+  (function() {
+    QUnit.test('should return a wrapped value', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = _.chain({ 'a': 0 });
+        assert.ok(actual instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return existing wrapped values', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _({ 'a': 0 });
+        assert.strictEqual(_.chain(wrapped), wrapped);
+        assert.strictEqual(wrapped.chain(), wrapped);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should enable chaining for methods that return unwrapped values', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var array = ['c', 'b', 'a'];
+
+        assert.ok(_.chain(array).head() instanceof _);
+        assert.ok(_(array).chain().head() instanceof _);
+
+        assert.ok(_.chain(array).isArray() instanceof _);
+        assert.ok(_(array).chain().isArray() instanceof _);
+
+        assert.ok(_.chain(array).sortBy().head() instanceof _);
+        assert.ok(_(array).chain().sortBy().head() instanceof _);
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+
+    QUnit.test('should chain multiple methods', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        lodashStable.times(2, function(index) {
+          var array = ['one two three four', 'five six seven eight', 'nine ten eleven twelve'],
+              expected = { ' ': 9, 'e': 14, 'f': 2, 'g': 1, 'h': 2, 'i': 4, 'l': 2, 'n': 6, 'o': 3, 'r': 2, 's': 2, 't': 5, 'u': 1, 'v': 4, 'w': 2, 'x': 1 },
+              wrapped = index ? _(array).chain() : _.chain(array);
+
+          var actual = wrapped
+            .chain()
+            .map(function(value) { return value.split(''); })
+            .flatten()
+            .reduce(function(object, chr) {
+              object[chr] || (object[chr] = 0);
+              object[chr]++;
+              return object;
+            }, {})
+            .value();
+
+          assert.deepEqual(actual, expected);
+
+          array = [1, 2, 3, 4, 5, 6];
+          wrapped = index ? _(array).chain() : _.chain(array);
+          actual = wrapped
+            .chain()
+            .filter(function(n) { return n % 2 != 0; })
+            .reject(function(n) { return n % 3 == 0; })
+            .sortBy(function(n) { return -n; })
+            .value();
+
+          assert.deepEqual(actual, [5, 1]);
+
+          array = [3, 4];
+          wrapped = index ? _(array).chain() : _.chain(array);
+          actual = wrapped
+            .reverse()
+            .concat([2, 1])
+            .unshift(5)
+            .tap(function(value) { value.pop(); })
+            .map(square)
+            .value();
+
+          assert.deepEqual(actual, [25, 16, 9, 4]);
+        });
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.chunk');
+
+  (function() {
+    var array = [0, 1, 2, 3, 4, 5];
+
+    QUnit.test('should return chunked arrays', function(assert) {
+      assert.expect(1);
+
+      var actual = _.chunk(array, 3);
+      assert.deepEqual(actual, [[0, 1, 2], [3, 4, 5]]);
+    });
+
+    QUnit.test('should return the last chunk as remaining elements', function(assert) {
+      assert.expect(1);
+
+      var actual = _.chunk(array, 4);
+      assert.deepEqual(actual, [[0, 1, 2, 3], [4, 5]]);
+    });
+
+    QUnit.test('should treat falsey `size` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? [[0], [1], [2], [3], [4], [5]] : [];
+      });
+
+      var actual = lodashStable.map(falsey, function(size, index) {
+        return index ? _.chunk(array, size) : _.chunk(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should ensure the minimum `size` is `0`', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.reject(falsey, lodashStable.isUndefined).concat(-1, -Infinity),
+          expected = lodashStable.map(values, alwaysEmptyArray);
+
+      var actual = lodashStable.map(values, function(n) {
+        return _.chunk(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce `size` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.chunk(array, array.length / 4), [[0], [1], [2], [3], [4], [5]]);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map([[1, 2], [3, 4]], _.chunk);
+      assert.deepEqual(actual, [[[1], [2]], [[3], [4]]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.clamp');
+
+  (function() {
+    QUnit.test('should work with a `max` argument', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.clamp(5, 3), 3);
+      assert.strictEqual(_.clamp(1, 3), 1);
+    });
+
+    QUnit.test('should clamp negative numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.clamp(-10, -5, 5), -5);
+      assert.strictEqual(_.clamp(-10.2, -5.5, 5.5), -5.5);
+      assert.strictEqual(_.clamp(-Infinity, -5, 5), -5);
+    });
+
+    QUnit.test('should clamp positive numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.clamp(10, -5, 5), 5);
+      assert.strictEqual(_.clamp(10.6, -5.6, 5.4), 5.4);
+      assert.strictEqual(_.clamp(Infinity, -5, 5), 5);
+    });
+
+    QUnit.test('should not alter negative numbers in range', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.clamp(-4, -5, 5), -4);
+      assert.strictEqual(_.clamp(-5, -5, 5), -5);
+      assert.strictEqual(_.clamp(-5.5, -5.6, 5.6), -5.5);
+    });
+
+    QUnit.test('should not alter positive numbers in range', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.clamp(4, -5, 5), 4);
+      assert.strictEqual(_.clamp(5, -5, 5), 5);
+      assert.strictEqual(_.clamp(4.5, -5.1, 5.2), 4.5);
+    });
+
+    QUnit.test('should not alter `0` in range', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(1 / _.clamp(0, -5, 5), Infinity);
+    });
+
+    QUnit.test('should clamp to `0`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(1 / _.clamp(-10, 0, 5), Infinity);
+    });
+
+    QUnit.test('should not alter `-0` in range', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(1 / _.clamp(-0, -5, 5), -Infinity);
+    });
+
+    QUnit.test('should clamp to `-0`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(1 / _.clamp(-10, -0, 5), -Infinity);
+    });
+
+    QUnit.test('should return `NaN` when `number` is `NaN`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.clamp(NaN, -5, 5), NaN);
+    });
+
+    QUnit.test('should coerce `min` and `max` of `NaN` to `0`', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.clamp(1, -5, NaN), 0);
+      assert.deepEqual(_.clamp(-1, NaN, 5), 0);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('clone methods');
+
+  (function() {
+    function Foo() {
+      this.a = 1;
+    }
+    Foo.prototype.b = 1;
+    Foo.c = function() {};
+
+    if (Map) {
+      var map = new Map;
+      map.set('a', 1);
+      map.set('b', 2);
+    }
+    if (Set) {
+      var set = new Set;
+      set.add(1);
+      set.add(2);
+    }
+    var objects = {
+      '`arguments` objects': arguments,
+      'arrays': ['a', ''],
+      'array-like-objects': { '0': 'a', '1': '', 'length': 3 },
+      'booleans': false,
+      'boolean objects': Object(false),
+      'date objects': new Date,
+      'Foo instances': new Foo,
+      'objects': { 'a': 0, 'b': 1, 'c': 2 },
+      'objects with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
+      'objects from another document': realm.object || {},
+      'maps': map,
+      'null values': null,
+      'numbers': 0,
+      'number objects': Object(0),
+      'regexes': /a/gim,
+      'sets': set,
+      'strings': 'a',
+      'string objects': Object('a'),
+      'undefined values': undefined
+    };
+
+    objects.arrays.length = 3;
+
+    var uncloneable = {
+      'DOM elements': body,
+      'functions': Foo,
+      'generators': generator
+    };
+
+    lodashStable.each(errors, function(error) {
+      uncloneable[error.name + 's'] = error;
+    });
+
+    QUnit.test('`_.clone` should perform a shallow clone', function(assert) {
+      assert.expect(2);
+
+      var array = [{ 'a': 0 }, { 'b': 1 }],
+          actual = _.clone(array);
+
+      assert.deepEqual(actual, array);
+      assert.ok(actual !== array && actual[0] === array[0]);
+    });
+
+    QUnit.test('`_.cloneDeep` should deep clone objects with circular references', function(assert) {
+      assert.expect(1);
+
+      var object = {
+        'foo': { 'b': { 'c': { 'd': {} } } },
+        'bar': {}
+      };
+
+      object.foo.b.c.d = object;
+      object.bar.b = object.foo.b;
+
+      var actual = _.cloneDeep(object);
+      assert.ok(actual.bar.b === actual.foo.b && actual === actual.foo.b.c.d && actual !== object);
+    });
+
+    QUnit.test('`_.cloneDeep` should deep clone objects with lots of circular references', function(assert) {
+      assert.expect(2);
+
+      var cyclical = {};
+      lodashStable.times(LARGE_ARRAY_SIZE + 1, function(index) {
+        cyclical['v' + index] = [index ? cyclical['v' + (index - 1)] : cyclical];
+      });
+
+      var clone = _.cloneDeep(cyclical),
+          actual = clone['v' + LARGE_ARRAY_SIZE][0];
+
+      assert.strictEqual(actual, clone['v' + (LARGE_ARRAY_SIZE - 1)]);
+      assert.notStrictEqual(actual, cyclical['v' + (LARGE_ARRAY_SIZE - 1)]);
+    });
+
+    QUnit.test('`_.cloneDeepWith` should provide `stack` to `customizer`', function(assert) {
+      assert.expect(164);
+
+      var Stack,
+          keys = [null, undefined, false, true, 1, -Infinity, NaN, {}, 'a', symbol || {}];
+
+      var pairs = lodashStable.map(keys, function(key, index) {
+        var lastIndex = keys.length - 1;
+        return [key, keys[lastIndex - index]];
+      });
+
+      _.cloneDeepWith({ 'a': 1 }, function() {
+        if (arguments.length > 1) {
+          Stack || (Stack = _.last(arguments).constructor);
+        }
+      });
+
+      var stacks = [new Stack(pairs), new Stack(pairs)];
+
+      lodashStable.times(LARGE_ARRAY_SIZE - pairs.length + 1, function() {
+        stacks[1].set({}, {});
+      });
+
+      lodashStable.each(stacks, function(stack) {
+        lodashStable.each(keys, function(key, index) {
+          var value = pairs[index][1];
+
+          assert.deepEqual(stack.get(key), value);
+          assert.strictEqual(stack.has(key), true);
+          assert.strictEqual(stack['delete'](key), true);
+          assert.strictEqual(stack.has(key), false);
+          assert.strictEqual(stack.get(key), undefined);
+          assert.strictEqual(stack['delete'](key), false);
+          assert.strictEqual(stack.set(key, value), stack);
+          assert.strictEqual(stack.has(key), true);
+        });
+
+        assert.strictEqual(stack.clear(), undefined);
+        assert.ok(lodashStable.every(keys, function(key) {
+          return !stack.has(key);
+        }));
+      });
+    });
+
+    lodashStable.each(['clone', 'cloneDeep'], function(methodName) {
+      var func = _[methodName],
+          isDeep = methodName == 'cloneDeep';
+
+      lodashStable.forOwn(objects, function(object, key) {
+        QUnit.test('`_.' + methodName + '` should clone ' + key, function(assert) {
+          assert.expect(2);
+
+          var isEqual = (key == 'maps' || key == 'sets') ? _.isEqual : lodashStable.isEqual,
+              actual = func(object);
+
+          assert.ok(isEqual(actual, object));
+
+          if (lodashStable.isObject(object)) {
+            assert.notStrictEqual(actual, object);
+          } else {
+            assert.strictEqual(actual, object);
+          }
+        });
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone array buffers', function(assert) {
+        assert.expect(2);
+
+        if (ArrayBuffer) {
+          var actual = func(arrayBuffer);
+          assert.strictEqual(actual.byteLength, arrayBuffer.byteLength);
+          assert.notStrictEqual(actual, arrayBuffer);
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone buffers', function(assert) {
+        assert.expect(4);
+
+        if (Buffer) {
+          var buffer = new Buffer([1, 2]),
+              actual = func(buffer);
+
+          assert.strictEqual(actual.byteLength, buffer.byteLength);
+          assert.strictEqual(actual.inspect(), buffer.inspect());
+          assert.notStrictEqual(actual, buffer);
+
+          buffer[0] = 2;
+          assert.strictEqual(actual[0], isDeep ? 2 : 1);
+        }
+        else {
+          skipAssert(assert, 4);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone `index` and `input` array properties', function(assert) {
+        assert.expect(2);
+
+        var array = /c/.exec('abcde'),
+            actual = func(array);
+
+        assert.strictEqual(actual.index, 2);
+        assert.strictEqual(actual.input, 'abcde');
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone `lastIndex` regexp property', function(assert) {
+        assert.expect(1);
+
+        var regexp = /c/g;
+        regexp.exec('abcde');
+
+        assert.strictEqual(func(regexp).lastIndex, 3);
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone expando properties', function(assert) {
+        assert.expect(1);
+
+        var values = lodashStable.map([false, true, 1, 'a'], function(value) {
+          var object = Object(value);
+          object.a = 1;
+          return object;
+        });
+
+        var expected = lodashStable.map(values, alwaysTrue);
+
+        var actual = lodashStable.map(values, function(value) {
+          return func(value).a === 1;
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone prototype objects', function(assert) {
+        assert.expect(2);
+
+        var actual = func(Foo.prototype);
+
+        assert.notOk(actual instanceof Foo);
+        assert.deepEqual(actual, { 'b': 1 });
+      });
+
+      QUnit.test('`_.' + methodName + '` should set the `[[Prototype]]` of a clone', function(assert) {
+        assert.expect(1);
+
+        assert.ok(func(new Foo) instanceof Foo);
+      });
+
+      QUnit.test('`_.' + methodName + '` should set the `[[Prototype]]` of a clone even when the `constructor` is incorrect', function(assert) {
+        assert.expect(1);
+
+        Foo.prototype.constructor = Object;
+        assert.ok(func(new Foo) instanceof Foo);
+        Foo.prototype.constructor = Foo;
+      });
+
+      QUnit.test('`_.' + methodName + '` should ensure `value` constructor is a function before using its `[[Prototype]]`', function(assert) {
+        assert.expect(1);
+
+        Foo.prototype.constructor = null;
+        assert.notOk(func(new Foo) instanceof Foo);
+        Foo.prototype.constructor = Foo;
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone properties that shadow those on `Object.prototype`', function(assert) {
+        assert.expect(2);
+
+        var object = {
+          'constructor': objectProto.constructor,
+          'hasOwnProperty': objectProto.hasOwnProperty,
+          'isPrototypeOf': objectProto.isPrototypeOf,
+          'propertyIsEnumerable': objectProto.propertyIsEnumerable,
+          'toLocaleString': objectProto.toLocaleString,
+          'toString': objectProto.toString,
+          'valueOf': objectProto.valueOf
+        };
+
+        var actual = func(object);
+
+        assert.deepEqual(actual, object);
+        assert.notStrictEqual(actual, object);
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone symbol properties', function(assert) {
+        assert.expect(3);
+
+        function Foo() {
+          this[symbol] = { 'c': 1 };
+        }
+
+        if (Symbol) {
+          var symbol2 = Symbol('b');
+          Foo.prototype[symbol2] = 2;
+
+          var object = { 'a': { 'b': new Foo } };
+          object[symbol] = { 'b': 1 };
+
+          var actual = func(object);
+
+          assert.deepEqual(getSymbols(actual.a.b), [symbol]);
+
+          if (isDeep) {
+            assert.deepEqual(actual[symbol], object[symbol]);
+            assert.deepEqual(actual.a.b[symbol], object.a.b[symbol]);
+          }
+          else {
+            assert.strictEqual(actual[symbol], object[symbol]);
+            assert.strictEqual(actual.a, object.a);
+          }
+        }
+        else {
+          skipAssert(assert, 3);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should clone symbol objects', function(assert) {
+        assert.expect(4);
+
+        if (Symbol) {
+          assert.strictEqual(func(symbol), symbol);
+
+          var object = Object(symbol),
+              actual = func(object);
+
+          assert.strictEqual(typeof actual, 'object');
+          assert.strictEqual(typeof actual.valueOf(), 'symbol');
+          assert.notStrictEqual(actual, object);
+        }
+        else {
+          skipAssert(assert, 4);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should not clone symbol primitives', function(assert) {
+        assert.expect(1);
+
+        if (Symbol) {
+          assert.strictEqual(func(symbol), symbol);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should not error on DOM elements', function(assert) {
+        assert.expect(1);
+
+        if (document) {
+          var element = document.createElement('div');
+
+          try {
+            assert.deepEqual(func(element), {});
+          } catch (e) {
+            assert.ok(false, e.message);
+          }
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should create an object from the same realm as `value`', function(assert) {
+        assert.expect(1);
+
+        var props = [];
+
+        var objects = lodashStable.transform(_, function(result, value, key) {
+          if (lodashStable.startsWith(key, '_') && lodashStable.isObject(value) &&
+              !lodashStable.isArguments(value) && !lodashStable.isElement(value) &&
+              !lodashStable.isFunction(value)) {
+            props.push(lodashStable.capitalize(lodashStable.camelCase(key)));
+            result.push(value);
+          }
+        }, []);
+
+        var expected = lodashStable.map(objects, alwaysTrue);
+
+        var actual = lodashStable.map(objects, function(object) {
+          var Ctor = object.constructor,
+              result = func(object);
+
+          return result !== object && ((result instanceof Ctor) || !(new Ctor instanceof Ctor));
+        });
+
+        assert.deepEqual(actual, expected, props.join(', '));
+      });
+
+      QUnit.test('`_.' + methodName + '` should perform a ' + (isDeep ? 'deep' : 'shallow') + ' clone when used as an iteratee for methods like `_.map`', function(assert) {
+        assert.expect(2);
+
+        var expected = [{ 'a': [0] }, { 'b': [1] }],
+            actual = lodashStable.map(expected, func);
+
+        assert.deepEqual(actual, expected);
+
+        if (isDeep) {
+          assert.ok(actual[0] !== expected[0] && actual[0].a !== expected[0].a && actual[1].b !== expected[1].b);
+        } else {
+          assert.ok(actual[0] !== expected[0] && actual[0].a === expected[0].a && actual[1].b === expected[1].b);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should return a unwrapped value when chaining', function(assert) {
+        assert.expect(2);
+
+        if (!isNpm) {
+          var object = objects.objects,
+              actual = _(object)[methodName]();
+
+          assert.deepEqual(actual, object);
+          assert.notStrictEqual(actual, object);
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+
+      lodashStable.each(arrayViews, function(type) {
+        QUnit.test('`_.' + methodName + '` should clone ' + type + ' values', function(assert) {
+          assert.expect(10);
+
+          var Ctor = root[type];
+
+          lodashStable.times(2, function(index) {
+            if (Ctor) {
+              var buffer = new ArrayBuffer(24),
+                  view = index ? new Ctor(buffer, 8, 1) : new Ctor(buffer),
+                  actual = func(view);
+
+              assert.deepEqual(actual, view);
+              assert.notStrictEqual(actual, view);
+              assert.strictEqual(actual.buffer === view.buffer, !isDeep);
+              assert.strictEqual(actual.byteOffset, view.byteOffset);
+              assert.strictEqual(actual.length, view.length);
+            }
+            else {
+              skipAssert(assert, 5);
+            }
+          });
+        });
+      });
+
+      lodashStable.forOwn(uncloneable, function(value, key) {
+        QUnit.test('`_.' + methodName + '` should not clone ' + key, function(assert) {
+          assert.expect(3);
+
+          if (value) {
+            var object = { 'a': value, 'b': { 'c': value } },
+                actual = func(object),
+                expected = (typeof value == 'function' && !!value.c) ? { 'c': Foo.c } : {};
+
+            assert.deepEqual(actual, object);
+            assert.notStrictEqual(actual, object);
+            assert.deepEqual(func(value), expected);
+          }
+          else {
+            skipAssert(assert, 3);
+          }
+        });
+      });
+    });
+
+    lodashStable.each(['cloneWith', 'cloneDeepWith'], function(methodName) {
+      var func = _[methodName],
+          isDeep = methodName == 'cloneDeepWith';
+
+      QUnit.test('`_.' + methodName + '` should provide the correct `customizer` arguments', function(assert) {
+        assert.expect(1);
+
+        var argsList = [],
+            object = new Foo;
+
+        func(object, function() {
+          var length = arguments.length,
+              args = slice.call(arguments, 0, length - (length > 1 ? 1 : 0));
+
+          argsList.push(args);
+        });
+
+        assert.deepEqual(argsList, isDeep ? [[object], [1, 'a', object]] : [[object]]);
+      });
+
+      QUnit.test('`_.' + methodName + '` should handle cloning if `customizer` returns `undefined`', function(assert) {
+        assert.expect(1);
+
+        var actual = func({ 'a': { 'b': 'c' } }, noop);
+        assert.deepEqual(actual, { 'a': { 'b': 'c' } });
+      });
+
+      lodashStable.forOwn(uncloneable, function(value, key) {
+        QUnit.test('`_.' + methodName + '` should work with a `customizer` callback and ' + key, function(assert) {
+          assert.expect(4);
+
+          var customizer = function(value) {
+            return lodashStable.isPlainObject(value) ? undefined : value;
+          };
+
+          var actual = func(value, customizer);
+
+          assert.deepEqual(actual, value);
+          assert.strictEqual(actual, value);
+
+          var object = { 'a': value, 'b': { 'c': value } };
+          actual = func(object, customizer);
+
+          assert.deepEqual(actual, object);
+          assert.notStrictEqual(actual, object);
+        });
+      });
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.compact');
+
+  (function() {
+    var largeArray = lodashStable.range(LARGE_ARRAY_SIZE).concat(null);
+
+    QUnit.test('should filter falsey values', function(assert) {
+      assert.expect(1);
+
+      var array = ['0', '1', '2'];
+      assert.deepEqual(_.compact(falsey.concat(array)), array);
+    });
+
+    QUnit.test('should work when in-between lazy operators', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var actual = _(falsey).thru(_.slice).compact().thru(_.slice).value();
+        assert.deepEqual(actual, []);
+
+        actual = _(falsey).thru(_.slice).push(true, 1).compact().push('a').value();
+        assert.deepEqual(actual, [true, 1, 'a']);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = _(largeArray).slice(1).compact().reverse().take().value();
+        assert.deepEqual(actual, _.take(_.compact(_.slice(largeArray, 1)).reverse()));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence with a custom `_.iteratee`', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var iteratee = _.iteratee,
+            pass = false;
+
+        _.iteratee = identity;
+
+        try {
+          var actual = _(largeArray).slice(1).compact().value();
+          pass = lodashStable.isEqual(actual, _.compact(_.slice(largeArray, 1)));
+        } catch (e) {console.log(e);}
+
+        assert.ok(pass);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.concat');
+
+  (function() {
+    QUnit.test('should shallow clone `array`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          actual = _.concat(array);
+
+      assert.deepEqual(actual, array);
+      assert.notStrictEqual(actual, array);
+    });
+
+    QUnit.test('should concat arrays and values', function(assert) {
+      assert.expect(2);
+
+      var array = [1],
+          actual = _.concat(array, 2, [3], [[4]]);
+
+      assert.deepEqual(actual, [1, 2, 3, [4]]);
+      assert.deepEqual(array, [1]);
+    });
+
+    QUnit.test('should cast non-array `array` values to arrays', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined, false, true, 1, NaN, 'a'];
+
+      var expected = lodashStable.map(values, function(value, index) {
+        return index ? [value] : [];
+      });
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.concat(value) : _.concat();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(values, function(value) {
+        return [value, 2, [3]];
+      });
+
+      actual = lodashStable.map(values, function(value) {
+        return _.concat(value, [2], [[3]]);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat sparse arrays as dense', function(assert) {
+      assert.expect(3);
+
+      var expected = [],
+          actual = _.concat(Array(1), Array(1));
+
+      expected.push(undefined, undefined);
+
+      assert.ok('0'in actual);
+      assert.ok('1' in actual);
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return a new wrapped array', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array = [1],
+            wrapped = _(array).concat([2, 3]),
+            actual = wrapped.value();
+
+        assert.deepEqual(array, [1]);
+        assert.deepEqual(actual, [1, 2, 3]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.cond');
+
+  (function() {
+    QUnit.test('should create a conditional function', function(assert) {
+      assert.expect(3);
+
+      var cond = _.cond([
+        [lodashStable.matches({ 'a': 1 }),     alwaysA],
+        [lodashStable.matchesProperty('b', 1), alwaysB],
+        [lodashStable.property('c'),           alwaysC]
+      ]);
+
+      assert.strictEqual(cond({ 'a':  1, 'b': 2, 'c': 3 }), 'a');
+      assert.strictEqual(cond({ 'a':  0, 'b': 1, 'c': 2 }), 'b');
+      assert.strictEqual(cond({ 'a': -1, 'b': 0, 'c': 1 }), 'c');
+    });
+
+    QUnit.test('should provide arguments to functions', function(assert) {
+      assert.expect(2);
+
+      var args1,
+          args2,
+          expected = ['a', 'b', 'c'];
+
+      var cond = _.cond([[
+        function() { args1 || (args1 = slice.call(arguments)); return true; },
+        function() { args2 || (args2 = slice.call(arguments)); }
+      ]]);
+
+      cond('a', 'b', 'c');
+
+      assert.deepEqual(args1, expected);
+      assert.deepEqual(args2, expected);
+    });
+
+    QUnit.test('should work with predicate shorthands', function(assert) {
+      assert.expect(3);
+
+      var cond = _.cond([
+        [{ 'a': 1 }, alwaysA],
+        [['b', 1],   alwaysB],
+        ['c',        alwaysC]
+      ]);
+
+      assert.strictEqual(cond({ 'a':  1, 'b': 2, 'c': 3 }), 'a');
+      assert.strictEqual(cond({ 'a':  0, 'b': 1, 'c': 2 }), 'b');
+      assert.strictEqual(cond({ 'a': -1, 'b': 0, 'c': 1 }), 'c');
+    });
+
+    QUnit.test('should return `undefined` when no condition is met', function(assert) {
+      assert.expect(1);
+
+      var cond = _.cond([[alwaysFalse, alwaysA]]);
+      assert.strictEqual(cond({ 'a': 1 }), undefined);
+    });
+
+    QUnit.test('should throw a TypeError if `pairs` is not composed of functions', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([false, true], function(value) {
+        assert.raises(function() { _.cond([[alwaysTrue, value]])(); }, TypeError);
+      });
+    });
+
+    QUnit.test('should use `this` binding of function for `pairs`', function(assert) {
+      assert.expect(1);
+
+      var cond = _.cond([
+        [function(a) { return this[a]; }, function(a, b) { return this[b]; }]
+      ]);
+
+      var object = { 'cond': cond, 'a': 1, 'b': 2 };
+      assert.strictEqual(object.cond('a', 'b'), 2);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.conforms');
+
+  (function() {
+    var objects = [
+      { 'a': 1, 'b': 8 },
+      { 'a': 2, 'b': 4 },
+      { 'a': 3, 'b': 16 }
+    ];
+
+    QUnit.test('should create a function that checks if a given object conforms to `source`', function(assert) {
+      assert.expect(2);
+
+      var conforms = _.conforms({
+        'b': function(value) { return value > 4; }
+      });
+
+      var actual = lodashStable.filter(objects, conforms);
+      assert.deepEqual(actual, [objects[0], objects[2]]);
+
+      conforms = _.conforms({
+        'b': function(value) { return value > 8; },
+        'a': function(value) { return value > 1; }
+      });
+
+      actual = lodashStable.filter(objects, conforms);
+      assert.deepEqual(actual, [objects[2]]);
+    });
+
+    QUnit.test('should not match by inherited `source` properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = function(value) {
+          return value > 1;
+        };
+      }
+      Foo.prototype.b = function(value) {
+        return value > 8;
+      };
+
+      var conforms = _.conforms(new Foo),
+          actual = lodashStable.filter(objects, conforms);
+
+      assert.deepEqual(actual, [objects[1], objects[2]]);
+    });
+
+    QUnit.test('should not invoke `source` predicates for missing `object` properties', function(assert) {
+      assert.expect(2);
+
+      var count = 0;
+
+      var conforms = _.conforms({
+        'a': function() { count++; return true; }
+      });
+
+      assert.strictEqual(conforms({}), false);
+      assert.strictEqual(count, 0);
+    });
+
+    QUnit.test('should work with a function for `object`', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.a = 1;
+
+      function Bar() {}
+      Bar.a = 2;
+
+      var conforms = _.conforms({
+        'a': function(value) { return value > 1; }
+      });
+
+      assert.strictEqual(conforms(Foo), false);
+      assert.strictEqual(conforms(Bar), true);
+    });
+
+    QUnit.test('should work with a function for `source`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.a = function(value) { return value > 1; };
+
+      var objects = [{ 'a': 1 }, { 'a': 2 }],
+          actual = lodashStable.filter(objects, _.conforms(Foo));
+
+      assert.deepEqual(actual, [objects[1]]);
+    });
+
+    QUnit.test('should work with a non-plain `object`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var conforms = _.conforms({
+        'b': function(value) { return value > 1; }
+      });
+
+      assert.strictEqual(conforms(new Foo), true);
+    });
+
+    QUnit.test('should return `false` when `object` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var conforms = _.conforms({
+        'a': function(value) { return value > 1; }
+      });
+
+      var actual = lodashStable.map(values, function(value, index) {
+        try {
+          return index ? conforms(value) : conforms();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing an empty `source` to a nullish `object`', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysTrue),
+          conforms = _.conforms({});
+
+      var actual = lodashStable.map(values, function(value, index) {
+        try {
+          return index ? conforms(value) : conforms();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing an empty `source`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1 },
+          expected = lodashStable.map(empties, alwaysTrue);
+
+      var actual = lodashStable.map(empties, function(value) {
+        var conforms = _.conforms(value);
+        return conforms(object);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not change behavior if `source` is modified', function(assert) {
+      assert.expect(2);
+
+      var source = {
+        'a': function(value) { return value > 1; }
+      };
+
+      var object = { 'a': 2 },
+          conforms = _.conforms(source);
+
+      assert.strictEqual(conforms(object), true);
+
+      source.a = function(value) { return value < 2; };
+      assert.strictEqual(conforms(object), true);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.constant');
+
+  (function() {
+    QUnit.test('should create a function that returns `value`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1 },
+          values = Array(2).concat(empties, true, 1, 'a'),
+          constant = _.constant(object),
+          expected = lodashStable.map(values, function() { return true; });
+
+      var actual = lodashStable.map(values, function(value, index) {
+        if (index == 0) {
+          var result = constant();
+        } else if (index == 1) {
+          result = constant.call({});
+        } else {
+          result = constant(value);
+        }
+        return result === object;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with falsey values', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function() { return true; });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        var constant = index ? _.constant(value) : _.constant(),
+            result = constant();
+
+        return (result === value) || (result !== result && value !== value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _(true).constant();
+        assert.ok(wrapped instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.countBy');
+
+  (function() {
+    var array = [6.1, 4.2, 6.3];
+
+    QUnit.test('should transform keys by `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.countBy(array, Math.floor);
+      assert.deepEqual(actual, { '4': 1, '6': 2 });
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var array = [4, 6, 6],
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant({ '4': 1, '6':  2 }));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.countBy(array, value) : _.countBy(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var actual = _.countBy(['one', 'two', 'three'], 'length');
+      assert.deepEqual(actual, { '3': 2, '5': 1 });
+    });
+
+    QUnit.test('should only add values to own, not inherited, properties', function(assert) {
+      assert.expect(2);
+
+      var actual = _.countBy(array, function(n) {
+        return Math.floor(n) > 4 ? 'hasOwnProperty' : 'constructor';
+      });
+
+      assert.deepEqual(actual.constructor, 1);
+      assert.deepEqual(actual.hasOwnProperty, 2);
+    });
+
+    QUnit.test('should work with a number for `iteratee`', function(assert) {
+      assert.expect(2);
+
+      var array = [
+        [1, 'a'],
+        [2, 'a'],
+        [2, 'b']
+      ];
+
+      assert.deepEqual(_.countBy(array, 0), { '1': 1, '2': 2 });
+      assert.deepEqual(_.countBy(array, 1), { 'a': 2, 'b': 1 });
+    });
+
+    QUnit.test('should work with an object for `collection`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.countBy({ 'a': 6.1, 'b': 4.2, 'c': 6.3 }, Math.floor);
+      assert.deepEqual(actual, { '4': 1, '6': 2 });
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE).concat(
+          lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 2), LARGE_ARRAY_SIZE),
+          lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 1.5), LARGE_ARRAY_SIZE)
+        );
+
+        var actual = _(array).countBy().map(square).filter(isEven).take().value();
+
+        assert.deepEqual(actual, _.take(_.filter(_.map(_.countBy(array), square), isEven)));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.create');
+
+  (function() {
+    function Shape() {
+      this.x = 0;
+      this.y = 0;
+    }
+
+    function Circle() {
+      Shape.call(this);
+    }
+
+    QUnit.test('should create an object that inherits from the given `prototype` object', function(assert) {
+      assert.expect(3);
+
+      Circle.prototype = _.create(Shape.prototype);
+      Circle.prototype.constructor = Circle;
+
+      var actual = new Circle;
+
+      assert.ok(actual instanceof Circle);
+      assert.ok(actual instanceof Shape);
+      assert.notStrictEqual(Circle.prototype, Shape.prototype);
+    });
+
+    QUnit.test('should assign `properties` to the created object', function(assert) {
+      assert.expect(3);
+
+      var expected = { 'constructor': Circle, 'radius': 0 };
+      Circle.prototype = _.create(Shape.prototype, expected);
+
+      var actual = new Circle;
+
+      assert.ok(actual instanceof Circle);
+      assert.ok(actual instanceof Shape);
+      assert.deepEqual(Circle.prototype, expected);
+    });
+
+    QUnit.test('should assign own properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+        this.c = 3;
+      }
+      Foo.prototype.b = 2;
+
+      assert.deepEqual(_.create({}, new Foo), { 'a': 1, 'c': 3 });
+    });
+
+    QUnit.test('should assign properties that shadow those of `prototype`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      var object = _.create(new Foo, { 'a': 1 });
+      assert.deepEqual(lodashStable.keys(object), ['a']);
+    });
+
+    QUnit.test('should accept a falsey `prototype` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyObject);
+
+      var actual = lodashStable.map(falsey, function(prototype, index) {
+        return index ? _.create(prototype) : _.create();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should ignore primitive `prototype` arguments and use an empty object instead', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(primitives, alwaysTrue);
+
+      var actual = lodashStable.map(primitives, function(value, index) {
+        return lodashStable.isPlainObject(index ? _.create(value) : _.create());
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [{ 'a': 1 }, { 'a': 1 }, { 'a': 1 }],
+          expected = lodashStable.map(array, alwaysTrue),
+          objects = lodashStable.map(array, _.create);
+
+      var actual = lodashStable.map(objects, function(object) {
+        return object.a === 1 && !_.keys(object).length;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.curry');
+
+  (function() {
+    function fn(a, b, c, d) {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should curry based on the number of arguments given', function(assert) {
+      assert.expect(3);
+
+      var curried = _.curry(fn),
+          expected = [1, 2, 3, 4];
+
+      assert.deepEqual(curried(1)(2)(3)(4), expected);
+      assert.deepEqual(curried(1, 2)(3, 4), expected);
+      assert.deepEqual(curried(1, 2, 3, 4), expected);
+    });
+
+    QUnit.test('should allow specifying `arity`', function(assert) {
+      assert.expect(3);
+
+      var curried = _.curry(fn, 3),
+          expected = [1, 2, 3];
+
+      assert.deepEqual(curried(1)(2, 3), expected);
+      assert.deepEqual(curried(1, 2)(3), expected);
+      assert.deepEqual(curried(1, 2, 3), expected);
+    });
+
+    QUnit.test('should coerce `arity` to an integer', function(assert) {
+      assert.expect(2);
+
+      var values = ['0', 0.6, 'xyz'],
+          expected = lodashStable.map(values, alwaysEmptyArray);
+
+      var actual = lodashStable.map(values, function(arity) {
+        return _.curry(fn, arity)();
+      });
+
+      assert.deepEqual(actual, expected);
+      assert.deepEqual(_.curry(fn, '2')(1)(2), [1, 2]);
+    });
+
+    QUnit.test('should support placeholders', function(assert) {
+      assert.expect(4);
+
+      var curried = _.curry(fn),
+          ph = curried.placeholder;
+
+      assert.deepEqual(curried(1)(ph, 3)(ph, 4)(2), [1, 2, 3, 4]);
+      assert.deepEqual(curried(ph, 2)(1)(ph, 4)(3), [1, 2, 3, 4]);
+      assert.deepEqual(curried(ph, ph, 3)(ph, 2)(ph, 4)(1), [1, 2, 3, 4]);
+      assert.deepEqual(curried(ph, ph, ph, 4)(ph, ph, 3)(ph, 2)(1), [1, 2, 3, 4]);
+    });
+
+    QUnit.test('should persist placeholders', function(assert) {
+      assert.expect(1);
+
+      var curried = _.curry(fn),
+          ph = curried.placeholder,
+          actual = curried(ph, ph, ph, 'd')('a')(ph)('b')('c');
+
+      assert.deepEqual(actual, ['a', 'b', 'c', 'd']);
+    });
+
+    QUnit.test('should use `_.placeholder` when set', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var curried = _.curry(fn),
+            _ph = _.placeholder = {},
+            ph = curried.placeholder;
+
+        assert.deepEqual(curried(1)(_ph, 3)(ph, 4), [1, ph, 3, 4]);
+        delete _.placeholder;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should provide additional arguments after reaching the target arity', function(assert) {
+      assert.expect(3);
+
+      var curried = _.curry(fn, 3);
+      assert.deepEqual(curried(1)(2, 3, 4), [1, 2, 3, 4]);
+      assert.deepEqual(curried(1, 2)(3, 4, 5), [1, 2, 3, 4, 5]);
+      assert.deepEqual(curried(1, 2, 3, 4, 5, 6), [1, 2, 3, 4, 5, 6]);
+    });
+
+    QUnit.test('should create a function with a `length` of `0`', function(assert) {
+      assert.expect(6);
+
+      lodashStable.times(2, function(index) {
+        var curried = index ? _.curry(fn, 4) : _.curry(fn);
+        assert.strictEqual(curried.length, 0);
+        assert.strictEqual(curried(1).length, 0);
+        assert.strictEqual(curried(1, 2).length, 0);
+      });
+    });
+
+    QUnit.test('should ensure `new curried` is an instance of `func`', function(assert) {
+      assert.expect(2);
+
+      function Foo(value) {
+        return value && object;
+      }
+
+      var curried = _.curry(Foo),
+          object = {};
+
+      assert.ok(new curried(false) instanceof Foo);
+      assert.strictEqual(new curried(true), object);
+    });
+
+    QUnit.test('should not set a `this` binding', function(assert) {
+      assert.expect(9);
+
+      var fn = function(a, b, c) {
+        var value = this || {};
+        return [value[a], value[b], value[c]];
+      };
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          expected = [1, 2, 3];
+
+      assert.deepEqual(_.curry(_.bind(fn, object), 3)('a')('b')('c'), expected);
+      assert.deepEqual(_.curry(_.bind(fn, object), 3)('a', 'b')('c'), expected);
+      assert.deepEqual(_.curry(_.bind(fn, object), 3)('a', 'b', 'c'), expected);
+
+      assert.deepEqual(_.bind(_.curry(fn), object)('a')('b')('c'), Array(3));
+      assert.deepEqual(_.bind(_.curry(fn), object)('a', 'b')('c'), Array(3));
+      assert.deepEqual(_.bind(_.curry(fn), object)('a', 'b', 'c'), expected);
+
+      object.curried = _.curry(fn);
+      assert.deepEqual(object.curried('a')('b')('c'), Array(3));
+      assert.deepEqual(object.curried('a', 'b')('c'), Array(3));
+      assert.deepEqual(object.curried('a', 'b', 'c'), expected);
+    });
+
+    QUnit.test('should work with partialed methods', function(assert) {
+      assert.expect(2);
+
+      var curried = _.curry(fn),
+          expected = [1, 2, 3, 4];
+
+      var a = _.partial(curried, 1),
+          b = _.bind(a, null, 2),
+          c = _.partialRight(b, 4),
+          d = _.partialRight(b(3), 4);
+
+      assert.deepEqual(c(3), expected);
+      assert.deepEqual(d(), expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.curryRight');
+
+  (function() {
+    function fn(a, b, c, d) {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should curry based on the number of arguments given', function(assert) {
+      assert.expect(3);
+
+      var curried = _.curryRight(fn),
+          expected = [1, 2, 3, 4];
+
+      assert.deepEqual(curried(4)(3)(2)(1), expected);
+      assert.deepEqual(curried(3, 4)(1, 2), expected);
+      assert.deepEqual(curried(1, 2, 3, 4), expected);
+    });
+
+    QUnit.test('should allow specifying `arity`', function(assert) {
+      assert.expect(3);
+
+      var curried = _.curryRight(fn, 3),
+          expected = [1, 2, 3];
+
+      assert.deepEqual(curried(3)(1, 2), expected);
+      assert.deepEqual(curried(2, 3)(1), expected);
+      assert.deepEqual(curried(1, 2, 3), expected);
+    });
+
+    QUnit.test('should coerce `arity` to an integer', function(assert) {
+      assert.expect(2);
+
+      var values = ['0', 0.6, 'xyz'],
+          expected = lodashStable.map(values, alwaysEmptyArray);
+
+      var actual = lodashStable.map(values, function(arity) {
+        return _.curryRight(fn, arity)();
+      });
+
+      assert.deepEqual(actual, expected);
+      assert.deepEqual(_.curryRight(fn, '2')(1)(2), [2, 1]);
+    });
+
+    QUnit.test('should support placeholders', function(assert) {
+      assert.expect(4);
+
+      var curried = _.curryRight(fn),
+          expected = [1, 2, 3, 4],
+          ph = curried.placeholder;
+
+      assert.deepEqual(curried(4)(2, ph)(1, ph)(3), expected);
+      assert.deepEqual(curried(3, ph)(4)(1, ph)(2), expected);
+      assert.deepEqual(curried(ph, ph, 4)(ph, 3)(ph, 2)(1), expected);
+      assert.deepEqual(curried(ph, ph, ph, 4)(ph, ph, 3)(ph, 2)(1), expected);
+    });
+
+    QUnit.test('should persist placeholders', function(assert) {
+      assert.expect(1);
+
+      var curried = _.curryRight(fn),
+          ph = curried.placeholder,
+          actual = curried('a', ph, ph, ph)('b')(ph)('c')('d');
+
+      assert.deepEqual(actual, ['a', 'b', 'c', 'd']);
+    });
+
+    QUnit.test('should use `_.placeholder` when set', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var curried = _.curryRight(fn),
+            _ph = _.placeholder = {},
+            ph = curried.placeholder;
+
+        assert.deepEqual(curried(4)(2, _ph)(1, ph), [1, 2, ph, 4]);
+        delete _.placeholder;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should provide additional arguments after reaching the target arity', function(assert) {
+      assert.expect(3);
+
+      var curried = _.curryRight(fn, 3);
+      assert.deepEqual(curried(4)(1, 2, 3), [1, 2, 3, 4]);
+      assert.deepEqual(curried(4, 5)(1, 2, 3), [1, 2, 3, 4, 5]);
+      assert.deepEqual(curried(1, 2, 3, 4, 5, 6), [1, 2, 3, 4, 5, 6]);
+    });
+
+    QUnit.test('should create a function with a `length` of `0`', function(assert) {
+      assert.expect(6);
+
+      lodashStable.times(2, function(index) {
+        var curried = index ? _.curryRight(fn, 4) : _.curryRight(fn);
+        assert.strictEqual(curried.length, 0);
+        assert.strictEqual(curried(4).length, 0);
+        assert.strictEqual(curried(3, 4).length, 0);
+      });
+    });
+
+    QUnit.test('should ensure `new curried` is an instance of `func`', function(assert) {
+      assert.expect(2);
+
+      function Foo(value) {
+        return value && object;
+      }
+
+      var curried = _.curryRight(Foo),
+          object = {};
+
+      assert.ok(new curried(false) instanceof Foo);
+      assert.strictEqual(new curried(true), object);
+    });
+
+    QUnit.test('should not set a `this` binding', function(assert) {
+      assert.expect(9);
+
+      var fn = function(a, b, c) {
+        var value = this || {};
+        return [value[a], value[b], value[c]];
+      };
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          expected = [1, 2, 3];
+
+      assert.deepEqual(_.curryRight(_.bind(fn, object), 3)('c')('b')('a'), expected);
+      assert.deepEqual(_.curryRight(_.bind(fn, object), 3)('b', 'c')('a'), expected);
+      assert.deepEqual(_.curryRight(_.bind(fn, object), 3)('a', 'b', 'c'), expected);
+
+      assert.deepEqual(_.bind(_.curryRight(fn), object)('c')('b')('a'), Array(3));
+      assert.deepEqual(_.bind(_.curryRight(fn), object)('b', 'c')('a'), Array(3));
+      assert.deepEqual(_.bind(_.curryRight(fn), object)('a', 'b', 'c'), expected);
+
+      object.curried = _.curryRight(fn);
+      assert.deepEqual(object.curried('c')('b')('a'), Array(3));
+      assert.deepEqual(object.curried('b', 'c')('a'), Array(3));
+      assert.deepEqual(object.curried('a', 'b', 'c'), expected);
+    });
+
+    QUnit.test('should work with partialed methods', function(assert) {
+      assert.expect(2);
+
+      var curried = _.curryRight(fn),
+          expected = [1, 2, 3, 4];
+
+      var a = _.partialRight(curried, 4),
+          b = _.partialRight(a, 3),
+          c = _.bind(b, null, 1),
+          d = _.partial(b(2), 1);
+
+      assert.deepEqual(c(2), expected);
+      assert.deepEqual(d(), expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('curry methods');
+
+  lodashStable.each(['curry', 'curryRight'], function(methodName) {
+    var func = _[methodName],
+        fn = function(a, b) { return slice.call(arguments); },
+        isCurry = methodName == 'curry';
+
+    QUnit.test('`_.' + methodName + '` should not error on functions with the same name as lodash methods', function(assert) {
+      assert.expect(1);
+
+      function run(a, b) {
+        return a + b;
+      }
+
+      var curried = func(run);
+
+      try {
+        var actual = curried(1)(2);
+      } catch (e) {}
+
+      assert.strictEqual(actual, 3);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work for function names that shadow those on `Object.prototype`', function(assert) {
+      assert.expect(1);
+
+      var curried = _.curry(function hasOwnProperty(a, b, c) {
+        return [a, b, c];
+      });
+
+      var expected = [1, 2, 3];
+
+      assert.deepEqual(curried(1)(2)(3), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(2);
+
+      var array = [fn, fn, fn],
+          object = { 'a': fn, 'b': fn, 'c': fn };
+
+      lodashStable.each([array, object], function(collection) {
+        var curries = lodashStable.map(collection, func),
+            expected = lodashStable.map(collection, lodashStable.constant(isCurry ? ['a', 'b'] : ['b', 'a']));
+
+        var actual = lodashStable.map(curries, function(curried) {
+          return curried('a')('b');
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.debounce');
+
+  (function() {
+    QUnit.test('should debounce a function', function(assert) {
+      assert.expect(6);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var debounced = _.debounce(function(value) {
+        ++callCount;
+        return value;
+      }, 32);
+
+      var actual = [debounced(0), debounced(1), debounced(2)];
+      assert.deepEqual(actual, [undefined, undefined, undefined]);
+      assert.strictEqual(callCount, 0);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+
+        var actual = [debounced(3), debounced(4), debounced(5)];
+        assert.deepEqual(actual, [2, 2, 2]);
+        assert.strictEqual(callCount, 1);
+      }, 128);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        done();
+      }, 256);
+    });
+
+    QUnit.test('subsequent debounced calls return the last `func` result', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var debounced = _.debounce(identity, 32);
+      debounced('x');
+
+      setTimeout(function() {
+        assert.notEqual(debounced('y'), 'y');
+      }, 64);
+
+      setTimeout(function() {
+        assert.notEqual(debounced('z'), 'z');
+        done();
+      }, 128);
+    });
+
+    QUnit.test('should not immediately call `func` when `wait` is `0`', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          debounced = _.debounce(function() { ++callCount; }, 0);
+
+      debounced();
+      debounced();
+      assert.strictEqual(callCount, 0);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+        done();
+      }, 5);
+    });
+
+    QUnit.test('should apply default options', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          debounced = _.debounce(function() { callCount++; }, 32, {});
+
+      debounced();
+      assert.strictEqual(callCount, 0);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('should support a `leading` option', function(assert) {
+      assert.expect(4);
+
+      var done = assert.async();
+
+      var callCounts = [0, 0];
+
+      var withLeading = _.debounce(function() {
+        callCounts[0]++;
+      }, 32, { 'leading': true });
+
+      var withLeadingAndTrailing = _.debounce(function() {
+        callCounts[1]++;
+      }, 32, { 'leading': true });
+
+      withLeading();
+      assert.strictEqual(callCounts[0], 1);
+
+      withLeadingAndTrailing();
+      withLeadingAndTrailing();
+      assert.strictEqual(callCounts[1], 1);
+
+      setTimeout(function() {
+        assert.deepEqual(callCounts, [1, 2]);
+
+        withLeading();
+        assert.strictEqual(callCounts[0], 2);
+
+        done();
+      }, 64);
+    });
+
+    QUnit.test('subsequent leading debounced calls return the last `func` result', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var debounced = _.debounce(identity, 32, { 'leading': true, 'trailing': false }),
+          result = [debounced('x'), debounced('y')];
+
+      assert.deepEqual(result, ['x', 'x']);
+
+      setTimeout(function() {
+        var result = [debounced('a'), debounced('b')];
+        assert.deepEqual(result, ['a', 'a']);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('should support a `trailing` option', function(assert) {
+      assert.expect(4);
+
+      var done = assert.async();
+
+      var withCount = 0,
+          withoutCount = 0;
+
+      var withTrailing = _.debounce(function() {
+        withCount++;
+      }, 32, { 'trailing': true });
+
+      var withoutTrailing = _.debounce(function() {
+        withoutCount++;
+      }, 32, { 'trailing': false });
+
+      withTrailing();
+      assert.strictEqual(withCount, 0);
+
+      withoutTrailing();
+      assert.strictEqual(withoutCount, 0);
+
+      setTimeout(function() {
+        assert.strictEqual(withCount, 1);
+        assert.strictEqual(withoutCount, 0);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('should support a `maxWait` option', function(assert) {
+      assert.expect(4);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var debounced = _.debounce(function(value) {
+        ++callCount;
+        return value;
+      }, 32, { 'maxWait': 64 });
+
+      debounced();
+      debounced();
+      assert.strictEqual(callCount, 0);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+        debounced();
+        debounced();
+        assert.strictEqual(callCount, 1);
+      }, 128);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        done();
+      }, 256);
+    });
+
+    QUnit.test('should support `maxWait` in a tight loop', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var limit = (argv || isPhantom) ? 1000 : 320,
+          withCount = 0,
+          withoutCount = 0;
+
+      var withMaxWait = _.debounce(function() {
+        withCount++;
+      }, 64, { 'maxWait': 128 });
+
+      var withoutMaxWait = _.debounce(function() {
+        withoutCount++;
+      }, 96);
+
+      var start = +new Date;
+      while ((new Date - start) < limit) {
+        withMaxWait();
+        withoutMaxWait();
+      }
+      var actual = [Boolean(withoutCount), Boolean(withCount)];
+      setTimeout(function() {
+        assert.deepEqual(actual, [false, true]);
+        done();
+      }, 1);
+    });
+
+    QUnit.test('should queue a trailing call for subsequent debounced calls after `maxWait`', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var debounced = _.debounce(function() {
+        ++callCount;
+      }, 64, { 'maxWait': 64 });
+
+      debounced();
+
+      lodashStable.times(20, function(index) {
+        setTimeout(debounced, 54 + index);
+      });
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        done();
+      }, 160);
+    });
+
+    QUnit.test('should cancel `maxDelayed` when `delayed` is invoked', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var debounced = _.debounce(function() {
+        callCount++;
+      }, 32, { 'maxWait': 64 });
+
+      debounced();
+
+      setTimeout(function() {
+        debounced();
+        assert.strictEqual(callCount, 1);
+      }, 128);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        done();
+      }, 192);
+    });
+
+    QUnit.test('should invoke the trailing call with the correct arguments and `this` binding', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var actual,
+          callCount = 0,
+          object = {};
+
+      var debounced = _.debounce(function(value) {
+        actual = [this];
+        push.apply(actual, arguments);
+        return ++callCount != 2;
+      }, 32, { 'leading': true, 'maxWait': 64 });
+
+      while (true) {
+        if (!debounced.call(object, 'a')) {
+          break;
+        }
+      }
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        assert.deepEqual(actual, [object, 'a']);
+        done();
+      }, 64);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.deburr');
+
+  (function() {
+    QUnit.test('should convert latin-1 supplementary letters to basic latin', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(burredLetters, _.deburr);
+      assert.deepEqual(actual, deburredLetters);
+    });
+
+    QUnit.test('should not deburr latin-1 mathematical operators', function(assert) {
+      assert.expect(1);
+
+      var operators = ['\xd7', '\xf7'],
+          actual = lodashStable.map(operators, _.deburr);
+
+      assert.deepEqual(actual, operators);
+    });
+
+    QUnit.test('should deburr combining diacritical marks', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(comboMarks, lodashStable.constant('ei'));
+
+      var actual = lodashStable.map(comboMarks, function(chr) {
+        return _.deburr('e' + chr + 'i');
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.defaults');
+
+  (function() {
+    QUnit.test('should assign source properties if missing on `object`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.defaults({ 'a': 1 }, { 'a': 2, 'b': 2 }), { 'a': 1, 'b': 2 });
+    });
+
+    QUnit.test('should accept multiple sources', function(assert) {
+      assert.expect(2);
+
+      var expected = { 'a': 1, 'b': 2, 'c': 3 };
+      assert.deepEqual(_.defaults({ 'a': 1, 'b': 2 }, { 'b': 3 }, { 'c': 3 }), expected);
+      assert.deepEqual(_.defaults({ 'a': 1, 'b': 2 }, { 'b': 3, 'c': 3 }, { 'c': 2 }), expected);
+    });
+
+    QUnit.test('should not overwrite `null` values', function(assert) {
+      assert.expect(1);
+
+      var actual = _.defaults({ 'a': null }, { 'a': 1 });
+      assert.strictEqual(actual.a, null);
+    });
+
+    QUnit.test('should overwrite `undefined` values', function(assert) {
+      assert.expect(1);
+
+      var actual = _.defaults({ 'a': undefined }, { 'a': 1 });
+      assert.strictEqual(actual.a, 1);
+    });
+
+    QUnit.test('should assign properties that shadow those on `Object.prototype`', function(assert) {
+      assert.expect(2);
+
+      var object = {
+        'constructor': objectProto.constructor,
+        'hasOwnProperty': objectProto.hasOwnProperty,
+        'isPrototypeOf': objectProto.isPrototypeOf,
+        'propertyIsEnumerable': objectProto.propertyIsEnumerable,
+        'toLocaleString': objectProto.toLocaleString,
+        'toString': objectProto.toString,
+        'valueOf': objectProto.valueOf
+      };
+
+      var source = {
+        'constructor': 1,
+        'hasOwnProperty': 2,
+        'isPrototypeOf': 3,
+        'propertyIsEnumerable': 4,
+        'toLocaleString': 5,
+        'toString': 6,
+        'valueOf': 7
+      };
+
+      assert.deepEqual(_.defaults({}, source), source);
+      assert.deepEqual(_.defaults({}, object, source), object);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.defaultsDeep');
+
+  (function() {
+    QUnit.test('should deep assign source properties if missing on `object`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': 2 }, 'd': 4 },
+          source = { 'a': { 'b': 1, 'c': 3 }, 'e': 5 },
+          expected = { 'a': { 'b': 2, 'c': 3 }, 'd': 4, 'e': 5 };
+
+      assert.deepEqual(_.defaultsDeep(object, source), expected);
+    });
+
+    QUnit.test('should accept multiple sources', function(assert) {
+      assert.expect(2);
+
+      var source1 = { 'a': { 'b': 3 } },
+          source2 = { 'a': { 'c': 3 } },
+          source3 = { 'a': { 'b': 3, 'c': 3 } },
+          source4 = { 'a': { 'c': 4 } },
+          expected = { 'a': { 'b': 2, 'c': 3 } };
+
+      assert.deepEqual(_.defaultsDeep({ 'a': { 'b': 2 } }, source1, source2), expected);
+      assert.deepEqual(_.defaultsDeep({ 'a': { 'b': 2 } }, source3, source4), expected);
+    });
+
+    QUnit.test('should not overwrite `null` values', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': null } },
+          source = { 'a': { 'b': 2 } },
+          actual = _.defaultsDeep(object, source);
+
+      assert.strictEqual(actual.a.b, null);
+    });
+
+    QUnit.test('should not overwrite regexp values', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': /x/ } },
+          source = { 'a': { 'b': /y/ } },
+          actual = _.defaultsDeep(object, source);
+
+      assert.deepEqual(actual.a.b, /x/);
+    });
+
+    QUnit.test('should not convert function properties to objects', function(assert) {
+      assert.expect(2);
+
+      var actual = _.defaultsDeep({}, { 'a': noop });
+      assert.strictEqual(actual.a, noop);
+
+      actual = _.defaultsDeep({}, { 'a': { 'b': noop } });
+      assert.strictEqual(actual.a.b, noop);
+    });
+
+    QUnit.test('should overwrite `undefined` values', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': undefined } },
+          source = { 'a': { 'b': 2 } },
+          actual = _.defaultsDeep(object, source);
+
+      assert.strictEqual(actual.a.b, 2);
+    });
+
+    QUnit.test('should merge sources containing circular references', function(assert) {
+      assert.expect(2);
+
+      var object = {
+        'foo': { 'b': { 'c': { 'd': {} } } },
+        'bar': { 'a': 2 }
+      };
+
+      var source = {
+        'foo': { 'b': { 'c': { 'd': {} } } },
+        'bar': {}
+      };
+
+      object.foo.b.c.d = object;
+      source.foo.b.c.d = source;
+      source.bar.b = source.foo.b;
+
+      var actual = _.defaultsDeep(object, source);
+
+      assert.strictEqual(actual.bar.b, actual.foo.b);
+      assert.strictEqual(actual.foo.b.c.d, actual.foo.b.c.d.foo.b.c.d);
+    });
+
+    QUnit.test('should not modify sources', function(assert) {
+      assert.expect(3);
+
+      var source1 = { 'a': 1, 'b': { 'c': 2 } },
+          source2 = { 'b': { 'c': 3, 'd': 3 } },
+          actual = _.defaultsDeep({}, source1, source2);
+
+      assert.deepEqual(actual, { 'a': 1, 'b': { 'c': 2, 'd': 3 } });
+      assert.deepEqual(source1, { 'a': 1, 'b': { 'c': 2 } });
+      assert.deepEqual(source2, { 'b': { 'c': 3, 'd': 3 } });
+    });
+
+    QUnit.test('should not attempt a merge of a string into an array', function(assert) {
+      assert.expect(1);
+
+      var actual = _.defaultsDeep({ 'a': ['abc'] }, { 'a': 'abc' });
+      assert.deepEqual(actual, { 'a': ['abc'] });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.defer');
+
+  (function() {
+    QUnit.test('should defer `func` execution', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var pass = false;
+      _.defer(function() { pass = true; });
+
+      setTimeout(function() {
+        assert.ok(pass);
+        done();
+      }, 32);
+    });
+
+    QUnit.test('should provide additional arguments to `func`', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var args;
+
+      _.defer(function() {
+        args = slice.call(arguments);
+      }, 1, 2);
+
+      setTimeout(function() {
+        assert.deepEqual(args, [1, 2]);
+        done();
+      }, 32);
+    });
+
+    QUnit.test('should be cancelable', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var pass = true;
+
+      var timerId = _.defer(function() {
+        pass = false;
+      });
+
+      clearTimeout(timerId);
+
+      setTimeout(function() {
+        assert.ok(pass);
+        done();
+      }, 32);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.delay');
+
+  (function() {
+    QUnit.test('should delay `func` execution', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var pass = false;
+      _.delay(function() { pass = true; }, 32);
+
+      setTimeout(function() {
+        assert.notOk(pass);
+      }, 1);
+
+      setTimeout(function() {
+        assert.ok(pass);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('should provide additional arguments to `func`', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var args;
+
+      _.delay(function() {
+        args = slice.call(arguments);
+      }, 32, 1, 2);
+
+      setTimeout(function() {
+        assert.deepEqual(args, [1, 2]);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('should use a default `wait` of `0`', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var pass = false;
+
+      _.delay(function() {
+        pass = true;
+      });
+
+      assert.notOk(pass);
+
+      setTimeout(function() {
+        assert.ok(pass);
+        done();
+      }, 0);
+    });
+
+    QUnit.test('should be cancelable', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var pass = true;
+
+      var timerId = _.delay(function() {
+        pass = false;
+      }, 32);
+
+      clearTimeout(timerId);
+
+      setTimeout(function() {
+        assert.ok(pass);
+        done();
+      }, 64);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('difference methods');
+
+  lodashStable.each(['difference', 'differenceBy', 'differenceWith'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return the difference of the given arrays', function(assert) {
+      assert.expect(2);
+
+      var actual = func([1, 2, 3, 4, 5], [5, 2, 10]);
+      assert.deepEqual(actual, [1, 3, 4]);
+
+      actual = func([1, 2, 3, 4, 5], [5, 2, 10], [8, 4]);
+      assert.deepEqual(actual, [1, 3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      var array = [-0, 0];
+
+      var actual = lodashStable.map(array, function(value) {
+        return func(array, [value]);
+      });
+
+      assert.deepEqual(actual, [[], []]);
+
+      actual = lodashStable.map(func([-0, 1], [1]), lodashStable.toString);
+      assert.deepEqual(actual, ['0']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should match `NaN`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func([1, NaN, 3], [NaN, 5, NaN]), [1, 3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays', function(assert) {
+      assert.expect(1);
+
+      var array1 = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+          array2 = lodashStable.range(LARGE_ARRAY_SIZE),
+          a = {},
+          b = {},
+          c = {};
+
+      array1.push(a, b, c);
+      array2.push(b, c, a);
+
+      assert.deepEqual(func(array1, array2), [LARGE_ARRAY_SIZE]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      var array = [-0, 0];
+
+      var actual = lodashStable.map(array, function(value) {
+        var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(value));
+        return func(array, largeArray);
+      });
+
+      assert.deepEqual(actual, [[], []]);
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, alwaysOne);
+      actual = lodashStable.map(func([-0, 1], largeArray), lodashStable.toString);
+      assert.deepEqual(actual, ['0']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of `NaN`', function(assert) {
+      assert.expect(1);
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, alwaysNaN);
+      assert.deepEqual(func([1, NaN, 3], largeArray), [1, 3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of objects', function(assert) {
+      assert.expect(1);
+
+      var object1 = {},
+          object2 = {},
+          largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(object1));
+
+      assert.deepEqual(func([object1, object2], largeArray), [object2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore values that are not array-like', function(assert) {
+      assert.expect(3);
+
+      var array = [1, null, 3];
+
+      assert.deepEqual(func(args, 3, { '0': 1 }), [1, 2, 3]);
+      assert.deepEqual(func(null, array, 1), []);
+      assert.deepEqual(func(array, args, null), [null]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.differenceBy');
+
+  (function() {
+    QUnit.test('should accept an `iteratee` argument', function(assert) {
+      assert.expect(2);
+
+      var actual = _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], Math.floor);
+      assert.deepEqual(actual, [3.1, 1.3]);
+
+      actual = _.differenceBy([{ 'x': 2 }, { 'x': 1 }], [{ 'x': 1 }], 'x');
+      assert.deepEqual(actual, [{ 'x': 2 }]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.differenceBy([3.1, 2.2, 1.3], [4.4, 2.5], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [4.4]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.differenceWith');
+
+  (function() {
+    QUnit.test('should work with a `comparator` argument', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }],
+          actual = _.differenceWith(objects, [{ 'x': 1, 'y': 2 }], lodashStable.isEqual);
+
+      assert.deepEqual(actual, [objects[1]]);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var array = [-0, 1],
+          largeArray = lodashStable.times(LARGE_ARRAY_SIZE, alwaysOne),
+          others = [[1], largeArray],
+          expected = lodashStable.map(others, lodashStable.constant(['-0']));
+
+      var actual = lodashStable.map(others, function(other) {
+        return lodashStable.map(_.differenceWith(array, other, lodashStable.eq), lodashStable.toString);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.divide');
+
+  (function() {
+    QUnit.test('should divide two numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.divide(6, 4), 1.5);
+      assert.strictEqual(_.divide(-6, 4), -1.5);
+      assert.strictEqual(_.divide(-6, -4), 1.5);
+    });
+
+    QUnit.test('should coerce arguments to numbers', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.divide('6', '4'), 1.5);
+      assert.deepEqual(_.divide('x', 'y'), NaN);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.drop');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should drop the first two elements', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.drop(array, 2), [3]);
+    });
+
+    QUnit.test('should treat falsey `n` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? [2, 3] : array;
+      });
+
+      var actual = lodashStable.map(falsey, function(n) {
+        return _.drop(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return all elements when `n` < `1`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([0, -1, -Infinity], function(n) {
+        assert.deepEqual(_.drop(array, n), array);
+      });
+    });
+
+    QUnit.test('should return an empty array when `n` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) {
+        assert.deepEqual(_.drop(array, n), []);
+      });
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.drop(array, 1.6), [2, 3]);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.drop);
+
+      assert.deepEqual(actual, [[2, 3], [5, 6], [8, 9]]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1),
+            predicate = function(value) { values.push(value); return isEven(value); },
+            values = [],
+            actual = _(array).drop(2).drop().value();
+
+        assert.deepEqual(actual, array.slice(3));
+
+        actual = _(array).filter(predicate).drop(2).drop().value();
+        assert.deepEqual(values, array);
+        assert.deepEqual(actual, _.drop(_.drop(_.filter(array, predicate), 2)));
+
+        actual = _(array).drop(2).dropRight().drop().dropRight(2).value();
+        assert.deepEqual(actual, _.dropRight(_.drop(_.dropRight(_.drop(array, 2))), 2));
+
+        values = [];
+
+        actual = _(array).drop().filter(predicate).drop(2).dropRight().drop().dropRight(2).value();
+        assert.deepEqual(values, array.slice(1));
+        assert.deepEqual(actual, _.dropRight(_.drop(_.dropRight(_.drop(_.filter(_.drop(array), predicate), 2))), 2));
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.dropRight');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should drop the last two elements', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropRight(array, 2), [1]);
+    });
+
+    QUnit.test('should treat falsey `n` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? [1, 2] : array;
+      });
+
+      var actual = lodashStable.map(falsey, function(n) {
+        return _.dropRight(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return all elements when `n` < `1`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([0, -1, -Infinity], function(n) {
+        assert.deepEqual(_.dropRight(array, n), array);
+      });
+    });
+
+    QUnit.test('should return an empty array when `n` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) {
+        assert.deepEqual(_.dropRight(array, n), []);
+      });
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropRight(array, 1.6), [1, 2]);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.dropRight);
+
+      assert.deepEqual(actual, [[1, 2], [4, 5], [7, 8]]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1),
+            predicate = function(value) { values.push(value); return isEven(value); },
+            values = [],
+            actual = _(array).dropRight(2).dropRight().value();
+
+        assert.deepEqual(actual, array.slice(0, -3));
+
+        actual = _(array).filter(predicate).dropRight(2).dropRight().value();
+        assert.deepEqual(values, array);
+        assert.deepEqual(actual, _.dropRight(_.dropRight(_.filter(array, predicate), 2)));
+
+        actual = _(array).dropRight(2).drop().dropRight().drop(2).value();
+        assert.deepEqual(actual, _.drop(_.dropRight(_.drop(_.dropRight(array, 2))), 2));
+
+        values = [];
+
+        actual = _(array).dropRight().filter(predicate).dropRight(2).drop().dropRight().drop(2).value();
+        assert.deepEqual(values, array.slice(0, -1));
+        assert.deepEqual(actual, _.drop(_.dropRight(_.drop(_.dropRight(_.filter(_.dropRight(array), predicate), 2))), 2));
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.dropRightWhile');
+
+  (function() {
+    var array = [1, 2, 3, 4];
+
+    var objects = [
+      { 'a': 0, 'b': 0 },
+      { 'a': 1, 'b': 1 },
+      { 'a': 2, 'b': 2 }
+    ];
+
+    QUnit.test('should drop elements while `predicate` returns truthy', function(assert) {
+      assert.expect(1);
+
+      var actual = _.dropRightWhile(array, function(n) {
+        return n > 2;
+      });
+
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.dropRightWhile(array, function() {
+        args = slice.call(arguments);
+      });
+
+      assert.deepEqual(args, [4, 3, array]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropRightWhile(objects, { 'b': 2 }), objects.slice(0, 2));
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropRightWhile(objects, ['b', 2]), objects.slice(0, 2));
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropRightWhile(objects, 'b'), objects.slice(0, 1));
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _(array).dropRightWhile(function(n) {
+          return n > 2;
+        });
+
+        assert.ok(wrapped instanceof _);
+        assert.deepEqual(wrapped.value(), [1, 2]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.dropWhile');
+
+  (function() {
+    var array = [1, 2, 3, 4];
+
+    var objects = [
+      { 'a': 2, 'b': 2 },
+      { 'a': 1, 'b': 1 },
+      { 'a': 0, 'b': 0 }
+    ];
+
+    QUnit.test('should drop elements while `predicate` returns truthy', function(assert) {
+      assert.expect(1);
+
+      var actual = _.dropWhile(array, function(n) {
+        return n < 3;
+      });
+
+      assert.deepEqual(actual, [3, 4]);
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.dropWhile(array, function() {
+        args = slice.call(arguments);
+      });
+
+      assert.deepEqual(args, [1, 0, array]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropWhile(objects, { 'b': 2 }), objects.slice(1));
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropWhile(objects, ['b', 2]), objects.slice(1));
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.dropWhile(objects, 'b'), objects.slice(2));
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 3),
+            predicate = function(n) { return n < 3; },
+            expected = _.dropWhile(array, predicate),
+            wrapped = _(array).dropWhile(predicate);
+
+        assert.deepEqual(wrapped.value(), expected);
+        assert.deepEqual(wrapped.reverse().value(), expected.slice().reverse());
+        assert.strictEqual(wrapped.last(), _.last(expected));
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence with `drop`', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 3);
+
+        var actual = _(array)
+          .dropWhile(function(n) { return n == 1; })
+          .drop()
+          .dropWhile(function(n) { return n == 3; })
+          .value();
+
+        assert.deepEqual(actual, array.slice(3));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.endsWith');
+
+  (function() {
+    var string = 'abc';
+
+    QUnit.test('should return `true` if a string ends with `target`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.endsWith(string, 'c'), true);
+    });
+
+    QUnit.test('should return `false` if a string does not end with `target`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.endsWith(string, 'b'), false);
+    });
+
+    QUnit.test('should work with a `position` argument', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.endsWith(string, 'b', 2), true);
+    });
+
+    QUnit.test('should work with `position` >= `string.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 5, MAX_SAFE_INTEGER, Infinity], function(position) {
+        assert.strictEqual(_.endsWith(string, 'c', position), true);
+      });
+    });
+
+    QUnit.test('should treat falsey `position` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysTrue);
+
+      var actual = lodashStable.map(falsey, function(position) {
+        return _.endsWith(string, position === undefined ? 'c' : '', position);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat a negative `position` as `0`', function(assert) {
+      assert.expect(6);
+
+      lodashStable.each([-1, -3, -Infinity], function(position) {
+        assert.ok(lodashStable.every(string, function(chr) {
+          return _.endsWith(string, chr, position) === false;
+        }));
+        assert.strictEqual(_.endsWith(string, '', position), true);
+      });
+    });
+
+    QUnit.test('should coerce `position` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.endsWith(string, 'ab', 2.2), true);
+    });
+
+    QUnit.test('should return `true` when `target` is an empty string regardless of `position`', function(assert) {
+      assert.expect(1);
+
+      assert.ok(lodashStable.every([-Infinity, NaN, -3, -1, 0, 1, 2, 3, 5, MAX_SAFE_INTEGER, Infinity], function(position) {
+        return _.endsWith(string, '', position, true);
+      }));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.eq');
+
+  (function() {
+    QUnit.test('should perform a `SameValueZero` comparison of two values', function(assert) {
+      assert.expect(11);
+
+      assert.strictEqual(_.eq(), true);
+      assert.strictEqual(_.eq(undefined), true);
+      assert.strictEqual(_.eq(0, -0), true);
+      assert.strictEqual(_.eq(NaN, NaN), true);
+      assert.strictEqual(_.eq(1, 1), true);
+
+      assert.strictEqual(_.eq(null, undefined), false);
+      assert.strictEqual(_.eq(1, Object(1)), false);
+      assert.strictEqual(_.eq(1, '1'), false);
+      assert.strictEqual(_.eq(1, '1'), false);
+
+      var object = { 'a': 1 };
+      assert.strictEqual(_.eq(object, object), true);
+      assert.strictEqual(_.eq(object, { 'a': 1 }), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.escape');
+
+  (function() {
+    var escaped = '&amp;&lt;&gt;&quot;&#39;&#96;\/',
+        unescaped = '&<>"\'`\/';
+
+    escaped += escaped;
+    unescaped += unescaped;
+
+    QUnit.test('should escape values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.escape(unescaped), escaped);
+    });
+
+    QUnit.test('should not escape the "/" character', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.escape('/'), '/');
+    });
+
+    QUnit.test('should handle strings with nothing to escape', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.escape('abc'), 'abc');
+    });
+
+    QUnit.test('should escape the same characters unescaped by `_.unescape`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.escape(_.unescape(escaped)), escaped);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.escapeRegExp');
+
+  (function() {
+    var escaped = '\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\',
+        unescaped = '^$.*+?()[]{}|\\';
+
+    QUnit.test('should escape values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.escapeRegExp(unescaped + unescaped), escaped + escaped);
+    });
+
+    QUnit.test('should handle strings with nothing to escape', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.escapeRegExp('abc'), 'abc');
+    });
+
+    QUnit.test('should return an empty string for empty values', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined, ''],
+          expected = lodashStable.map(values, alwaysEmptyString);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.escapeRegExp(value) : _.escapeRegExp();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.every');
+
+  (function() {
+    QUnit.test('should return `true` if `predicate` returns truthy for all elements', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(lodashStable.every([true, 1, 'a'], identity), true);
+    });
+
+    QUnit.test('should return `true` for empty collections', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, alwaysTrue);
+
+      var actual = lodashStable.map(empties, function(value) {
+        try {
+          return _.every(value, identity);
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` as soon as `predicate` returns falsey', function(assert) {
+      assert.expect(2);
+
+      var count = 0;
+
+      assert.strictEqual(_.every([true, null, true], function(value) {
+        count++;
+        return value;
+      }), false);
+
+      assert.strictEqual(count, 2);
+    });
+
+    QUnit.test('should work with collections of `undefined` values (test in IE < 9)', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.every([undefined, undefined, undefined], identity), false);
+    });
+
+    QUnit.test('should use `_.identity` when `predicate` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var array = [0];
+        return index ? _.every(array, value) : _.every(array);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(values, alwaysTrue);
+      actual = lodashStable.map(values, function(value, index) {
+        var array = [1];
+        return index ? _.every(array, value) : _.every(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var objects = [{ 'a': 0, 'b': 1 }, { 'a': 1, 'b': 2 }];
+      assert.strictEqual(_.every(objects, 'a'), false);
+      assert.strictEqual(_.every(objects, 'b'), true);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(2);
+
+      var objects = [{ 'a': 0, 'b': 0 }, { 'a': 0, 'b': 1 }];
+      assert.strictEqual(_.every(objects, { 'a': 0 }), true);
+      assert.strictEqual(_.every(objects, { 'b': 1 }), false);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map([[1]], _.every);
+      assert.deepEqual(actual, [true]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('strict mode checks');
+
+  lodashStable.each(['assign', 'assignIn', 'bindAll', 'defaults'], function(methodName) {
+    var func = _[methodName],
+        isBindAll = methodName == 'bindAll';
+
+    QUnit.test('`_.' + methodName + '` should ' + (isStrict ? '' : 'not ') + 'throw strict mode errors', function(assert) {
+      assert.expect(1);
+
+      if (freeze) {
+        var object = freeze({ 'a': undefined, 'b': function() {} }),
+            pass = !isStrict;
+
+        try {
+          func(object, isBindAll ? 'b' : { 'a': 1 });
+        } catch (e) {
+          pass = !pass;
+        }
+        assert.ok(pass);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.fill');
+
+  (function() {
+    QUnit.test('should use a default `start` of `0` and a default `end` of `array.length`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(_.fill(array, 'a'), ['a', 'a', 'a']);
+    });
+
+    QUnit.test('should use `undefined` for `value` if not given', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          actual = _.fill(array);
+
+      assert.deepEqual(actual, Array(3));
+      assert.ok(lodashStable.every(actual, function(value, index) {
+        return index in actual;
+      }));
+    });
+
+    QUnit.test('should work with a positive `start`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(_.fill(array, 'a', 1), [1, 'a', 'a']);
+    });
+
+    QUnit.test('should work with a `start` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(start) {
+        var array = [1, 2, 3];
+        assert.deepEqual(_.fill(array, 'a', start), [1, 2, 3]);
+      });
+    });
+
+    QUnit.test('should treat falsey `start` values as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, lodashStable.constant(['a', 'a', 'a']));
+
+      var actual = lodashStable.map(falsey, function(start) {
+        var array = [1, 2, 3];
+        return _.fill(array, 'a', start);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with a negative `start`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(_.fill(array, 'a', -1), [1, 2, 'a']);
+    });
+
+    QUnit.test('should work with a negative `start` <= negative `array.length`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([-3, -4, -Infinity], function(start) {
+        var array = [1, 2, 3];
+        assert.deepEqual(_.fill(array, 'a', start), ['a', 'a', 'a']);
+      });
+    });
+
+    QUnit.test('should work with `start` >= `end`', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([2, 3], function(start) {
+        var array = [1, 2, 3];
+        assert.deepEqual(_.fill(array, 'a', start, 2), [1, 2, 3]);
+      });
+    });
+
+    QUnit.test('should work with a positive `end`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(_.fill(array, 'a', 0, 1), ['a', 2, 3]);
+    });
+
+    QUnit.test('should work with a `end` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(end) {
+        var array = [1, 2, 3];
+        assert.deepEqual(_.fill(array, 'a', 0, end), ['a', 'a', 'a']);
+      });
+    });
+
+    QUnit.test('should treat falsey `end` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? ['a', 'a', 'a'] : [1, 2, 3];
+      });
+
+      var actual = lodashStable.map(falsey, function(end) {
+        var array = [1, 2, 3];
+        return _.fill(array, 'a', 0, end);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with a negative `end`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(_.fill(array, 'a', 0, -1), ['a', 'a', 3]);
+    });
+
+    QUnit.test('should work with a negative `end` <= negative `array.length`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([-3, -4, -Infinity], function(end) {
+        var array = [1, 2, 3];
+        assert.deepEqual(_.fill(array, 'a', 0, end), [1, 2, 3]);
+      });
+    });
+
+    QUnit.test('should coerce `start` and `end` to integers', function(assert) {
+      assert.expect(1);
+
+      var positions = [[0.1, 1.6], ['0', 1], [0, '1'], ['1'], [NaN, 1], [1, NaN]];
+
+      var actual = lodashStable.map(positions, function(pos) {
+        var array = [1, 2, 3];
+        return _.fill.apply(_, [array, 'a'].concat(pos));
+      });
+
+      assert.deepEqual(actual, [['a', 2, 3], ['a', 2, 3], ['a', 2, 3], [1, 'a', 'a'], ['a', 2, 3], [1, 2, 3]]);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2], [3, 4]],
+          actual = lodashStable.map(array, _.fill);
+
+      assert.deepEqual(actual, [[0, 0], [1, 1]]);
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var array = [1, 2, 3],
+            wrapped = _(array).fill('a'),
+            actual = wrapped.value();
+
+        assert.ok(wrapped instanceof _);
+        assert.deepEqual(actual, ['a', 'a', 'a']);
+        assert.strictEqual(actual, array);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.filter');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should return elements `predicate` returns truthy for', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.filter(array, isEven), [2]);
+    });
+
+    QUnit.test('should iterate over an object with numeric keys (test in Mobile Safari 8)', function(assert) {
+      assert.expect(1);
+
+      // Trigger a mobile Safari 8 JIT bug.
+      // See https://github.com/lodash/lodash/issues/799.
+      var counter = 0,
+          object = { '1': 'foo', '8': 'bar', '50': 'baz' };
+
+      lodashStable.times(1000, function(assert) {
+        _.filter([], alwaysTrue);
+      });
+
+      _.filter(object, function() {
+        counter++;
+        return true;
+      });
+
+      assert.strictEqual(counter, 3);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  lodashStable.each(['find', 'findLast', 'findIndex', 'findLastIndex', 'findKey', 'findLastKey'], function(methodName) {
+    QUnit.module('lodash.' + methodName);
+
+    var func = _[methodName];
+
+    (function() {
+      var objects = [
+        { 'a': 0, 'b': 0 },
+        { 'a': 1, 'b': 1 },
+        { 'a': 2, 'b': 2 }
+      ];
+
+      var expected = ({
+        'find': [objects[1], undefined, objects[2], objects[1]],
+        'findLast': [objects[2], undefined, objects[2], objects[2]],
+        'findIndex': [1, -1, 2, 1],
+        'findLastIndex': [2, -1, 2, 2],
+        'findKey': ['1', undefined, '2', '1'],
+        'findLastKey': ['2', undefined, '2', '2']
+      })[methodName];
+
+      QUnit.test('`_.' + methodName + '` should return the found value', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(func(objects, function(object) { return object.a; }), expected[0]);
+      });
+
+      QUnit.test('`_.' + methodName + '` should return `' + expected[1] + '` if value is not found', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(func(objects, function(object) { return object.a === 3; }), expected[1]);
+      });
+
+      QUnit.test('`_.' + methodName + '` should work with `_.matches` shorthands', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(func(objects, { 'b': 2 }), expected[2]);
+      });
+
+      QUnit.test('`_.' + methodName + '` should work with `_.matchesProperty` shorthands', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(func(objects, ['b', 2]), expected[2]);
+      });
+
+      QUnit.test('`_.' + methodName + '` should work with `_.property` shorthands', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(func(objects, 'b'), expected[3]);
+      });
+
+      QUnit.test('`_.' + methodName + '` should return `' + expected[1] + '` for empty collections', function(assert) {
+        assert.expect(1);
+
+        var emptyValues = lodashStable.endsWith(methodName, 'Index') ? lodashStable.reject(empties, lodashStable.isPlainObject) : empties,
+            expecting = lodashStable.map(emptyValues, lodashStable.constant(expected[1]));
+
+        var actual = lodashStable.map(emptyValues, function(value) {
+          try {
+            return func(value, { 'a': 3 });
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expecting);
+      });
+    }());
+
+    (function() {
+      var array = [1, 2, 3, 4];
+
+      var expected = ({
+        'find': 1,
+        'findLast': 4,
+        'findIndex': 0,
+        'findLastIndex': 3,
+        'findKey': '0',
+        'findLastKey': '3'
+      })[methodName];
+
+      QUnit.test('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          assert.strictEqual(_(array)[methodName](), expected);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          assert.ok(_(array).chain()[methodName]() instanceof _);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should not execute immediately when explicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          var wrapped = _(array).chain()[methodName]();
+          assert.strictEqual(wrapped.__wrapped__, array);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should work in a lazy sequence', function(assert) {
+        assert.expect(2);
+
+        if (!isNpm) {
+          var largeArray = lodashStable.range(1, LARGE_ARRAY_SIZE + 1),
+              smallArray = array;
+
+          lodashStable.times(2, function(index) {
+            var array = index ? largeArray : smallArray,
+                wrapped = _(array).filter(isEven);
+
+            assert.strictEqual(wrapped[methodName](), func(lodashStable.filter(array, isEven)));
+          });
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+    }());
+
+    (function() {
+      var expected = ({
+        'find': 1,
+        'findLast': 2,
+        'findKey': 'a',
+        'findLastKey': 'b'
+      })[methodName];
+
+      if (expected != null) {
+        QUnit.test('`_.' + methodName + '` should work with an object for `collection`', function(assert) {
+          assert.expect(1);
+
+          var actual = func({ 'a': 1, 'b': 2, 'c': 3 }, function(n) {
+            return n < 3;
+          });
+
+          assert.strictEqual(actual, expected);
+        });
+      }
+    }());
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.find and lodash.findLast');
+
+  lodashStable.each(['find', 'findLast'], function(methodName) {
+    var isFind = methodName == 'find';
+
+    QUnit.test('`_.' + methodName + '` should support shortcut fusion', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var findCount = 0,
+            mapCount = 0,
+            array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1),
+            iteratee = function(value) { mapCount++; return square(value); },
+            predicate = function(value) { findCount++; return isEven(value); },
+            actual = _(array).map(iteratee)[methodName](predicate);
+
+        assert.strictEqual(findCount, isFind ? 2 : 1);
+        assert.strictEqual(mapCount, isFind ? 2 : 1);
+        assert.strictEqual(actual, isFind ? 4 : square(LARGE_ARRAY_SIZE));
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.flip');
+
+  (function() {
+    function fn() {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should flip arguments provided to `func`', function(assert) {
+      assert.expect(1);
+
+      var flipped = _.flip(fn);
+      assert.deepEqual(flipped('a', 'b', 'c', 'd'), ['d', 'c', 'b', 'a']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.flatMapDepth');
+
+  (function() {
+    var array = [1, [2, [3, [4]], 5]];
+
+    QUnit.test('should use a default `depth` of `1`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.flatMapDepth(array, identity), [1, 2, [3, [4]], 5]);
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([1, 2, [3, [4]], 5]));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.flatMapDepth(array, value) : _.flatMapDepth(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat a `depth` of < `1` as a shallow clone', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([-1, 0], function(depth) {
+        assert.deepEqual(_.flatMapDepth(array, identity, depth), [1, [2, [3, [4]], 5]]);
+      });
+    });
+
+    QUnit.test('should coerce `depth` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.flatMapDepth(array, identity, 2.2), [1, 2, 3, [4], 5]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('flatMap methods');
+
+  lodashStable.each(['flatMap', 'flatMapDeep', 'flatMapDepth'], function(methodName) {
+    var func = _[methodName],
+        array = [1, 2, 3, 4];
+
+    function duplicate(n) {
+      return [n, n];
+    }
+
+    QUnit.test('`_.' + methodName + '` should map values in `array` to a new flattened array', function(assert) {
+      assert.expect(1);
+
+      var actual = func(array, duplicate),
+          expected = lodashStable.flatten(lodashStable.map(array, duplicate));
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': [1, 2] }, { 'a': [3, 4] }];
+      assert.deepEqual(func(objects, 'a'), array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should iterate over own string keyed properties of objects', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = [1, 2];
+      }
+      Foo.prototype.b = [3, 4];
+
+      var actual = func(new Foo, identity);
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(2);
+
+      var array = [[1, 2], [3, 4]],
+          object = { 'a': [1, 2], 'b': [3, 4] },
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([1, 2, 3, 4]));
+
+      lodashStable.each([array, object], function(collection) {
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? func(collection, value) : func(collection);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should accept a falsey `collection` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyArray);
+
+      var actual = lodashStable.map(falsey, function(collection, index) {
+        try {
+          return index ? func(collection) : func();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat number values for `collection` as empty', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(1), []);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with objects with non-number length properties', function(assert) {
+      assert.expect(1);
+
+      var object = { 'length': [1, 2] };
+      assert.deepEqual(func(object, identity), [1, 2]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.flattenDepth');
+
+  (function() {
+    var array = [1, [2, [3, [4]], 5]];
+
+    QUnit.test('should use a default `depth` of `1`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.flattenDepth(array), [1, 2, [3, [4]], 5]);
+    });
+
+    QUnit.test('should treat a `depth` of < `1` as a shallow clone', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([-1, 0], function(depth) {
+        assert.deepEqual(_.flattenDepth(array, depth), [1, [2, [3, [4]], 5]]);
+      });
+    });
+
+    QUnit.test('should coerce `depth` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.flattenDepth(array, 2.2), [1, 2, 3, [4], 5]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('flatten methods');
+
+  (function() {
+    var args = arguments,
+        array = [1, [2, [3, [4]], 5]];
+
+    QUnit.test('should flatten `arguments` objects', function(assert) {
+      assert.expect(3);
+
+      var array = [args, [args]];
+
+      assert.deepEqual(_.flatten(array), [1, 2, 3, args]);
+      assert.deepEqual(_.flattenDeep(array), [1, 2, 3, 1, 2, 3]);
+      assert.deepEqual(_.flattenDepth(array, 2), [1, 2, 3, 1, 2, 3]);
+    });
+
+    QUnit.test('should treat sparse arrays as dense', function(assert) {
+      assert.expect(6);
+
+      var array = [[1, 2, 3], Array(3)],
+          expected = [1, 2, 3];
+
+      expected.push(undefined, undefined, undefined);
+
+      lodashStable.each([_.flatten(array), _.flattenDeep(array), _.flattenDepth(array)], function(actual) {
+        assert.deepEqual(actual, expected);
+        assert.ok('4' in actual);
+      });
+    });
+
+    QUnit.test('should work with extremely large arrays', function(assert) {
+      assert.expect(3);
+
+      lodashStable.times(3, function(index) {
+        var expected = Array(5e5);
+        try {
+          var func = _.flatten;
+          if (index == 1) {
+            func = _.flattenDeep;
+          } else if (index == 2) {
+            func = _.flattenDepth;
+          }
+          assert.deepEqual(func([expected]), expected);
+        } catch (e) {
+          assert.ok(false, e.message);
+        }
+      });
+    });
+
+    QUnit.test('should work with empty arrays', function(assert) {
+      assert.expect(3);
+
+      var array = [[], [[]], [[], [[[]]]]];
+
+      assert.deepEqual(_.flatten(array), [[], [], [[[]]]]);
+      assert.deepEqual(_.flattenDeep(array), []);
+      assert.deepEqual(_.flattenDepth(array, 2), [[[]]]);
+    });
+
+    QUnit.test('should support flattening of nested arrays', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(_.flatten(array), [1, 2, [3, [4]], 5]);
+      assert.deepEqual(_.flattenDeep(array), [1, 2, 3, 4, 5]);
+      assert.deepEqual(_.flattenDepth(array, 2), [1, 2, 3, [4], 5]);
+    });
+
+    QUnit.test('should return an empty array for non array-like objects', function(assert) {
+      assert.expect(3);
+
+      var expected = [],
+          nonArray = { 'a': 1 };
+
+      assert.deepEqual(_.flatten(nonArray), expected);
+      assert.deepEqual(_.flattenDeep(nonArray), expected);
+      assert.deepEqual(_.flattenDepth(nonArray, 2), expected);
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var wrapped = _(array),
+            actual = wrapped.flatten();
+
+        assert.ok(actual instanceof _);
+        assert.deepEqual(actual.value(), [1, 2, [3, [4]], 5]);
+
+        actual = wrapped.flattenDeep();
+
+        assert.ok(actual instanceof _);
+        assert.deepEqual(actual.value(), [1, 2, 3, 4, 5]);
+
+        actual = wrapped.flattenDepth(2);
+
+        assert.ok(actual instanceof _);
+        assert.deepEqual(actual.value(), [1, 2, 3, [4], 5]);
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('flow methods');
+
+  lodashStable.each(['flow', 'flowRight'], function(methodName) {
+    var func = _[methodName],
+        isFlow = methodName == 'flow';
+
+    QUnit.test('`_.' + methodName + '` should supply each function with the return value of the previous', function(assert) {
+      assert.expect(1);
+
+      var fixed = function(n) { return n.toFixed(1); },
+          combined = isFlow ? func(add, square, fixed) : func(fixed, square, add);
+
+      assert.strictEqual(combined(1, 2), '9.0');
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a new function', function(assert) {
+      assert.expect(1);
+
+      assert.notStrictEqual(func(noop), noop);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an identity function when no arguments are given', function(assert) {
+      assert.expect(6);
+
+      _.times(2, function(index) {
+        try {
+          var combined = index ? func([]) : func();
+          assert.strictEqual(combined('a'), 'a');
+        } catch (e) {
+          assert.ok(false, e.message);
+        }
+        assert.strictEqual(combined.length, 0);
+        assert.notStrictEqual(combined, identity);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a curried function and `_.head`', function(assert) {
+      assert.expect(1);
+
+      var curried = _.curry(identity);
+
+      var combined = isFlow
+        ? func(_.head, curried)
+        : func(curried, _.head);
+
+      assert.strictEqual(combined([1]), 1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support shortcut fusion', function(assert) {
+      assert.expect(6);
+
+      var filterCount,
+          mapCount,
+          array = lodashStable.range(LARGE_ARRAY_SIZE),
+          iteratee = function(value) { mapCount++; return square(value); },
+          predicate = function(value) { filterCount++; return isEven(value); };
+
+      lodashStable.times(2, function(index) {
+        var filter1 = _.filter,
+            filter2 = _.curry(_.rearg(_.ary(_.filter, 2), 1, 0), 2),
+            filter3 = (_.filter = index ? filter2 : filter1, filter2(predicate));
+
+        var map1 = _.map,
+            map2 = _.curry(_.rearg(_.ary(_.map, 2), 1, 0), 2),
+            map3 = (_.map = index ? map2 : map1, map2(iteratee));
+
+        var take1 = _.take,
+            take2 = _.curry(_.rearg(_.ary(_.take, 2), 1, 0), 2),
+            take3 = (_.take = index ? take2 : take1, take2(2));
+
+        var combined = isFlow
+          ? func(map3, filter3, _.compact, take3)
+          : func(take3, _.compact, filter3, map3);
+
+        filterCount = mapCount = 0;
+        assert.deepEqual(combined(array), [4, 16]);
+
+        if (!isNpm && WeakMap && WeakMap.name) {
+          assert.strictEqual(filterCount, 5, 'filterCount');
+          assert.strictEqual(mapCount, 5, 'mapCount');
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+        _.filter = filter1;
+        _.map = map1;
+        _.take = take1;
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with curried functions with placeholders', function(assert) {
+      assert.expect(1);
+
+      var curried = _.curry(_.ary(_.map, 2), 2),
+          getProp = curried(curried.placeholder, 'a'),
+          objects = [{ 'a': 1 }, { 'a': 2 }, { 'a': 1 }];
+
+      var combined = isFlow
+        ? func(getProp, _.uniq)
+        : func(_.uniq, getProp);
+
+      assert.deepEqual(combined(objects), [1, 2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _(noop)[methodName]();
+        assert.ok(wrapped instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.forEach');
+
+  (function() {
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.each, _.forEach);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.forEachRight');
+
+  (function() {
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.eachRight, _.forEachRight);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('forIn methods');
+
+  lodashStable.each(['forIn', 'forInRight'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` iterates over inherited string keyed properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var keys = [];
+      func(new Foo, function(value, key) { keys.push(key); });
+      assert.deepEqual(keys.sort(), ['a', 'b']);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('forOwn methods');
+
+  lodashStable.each(['forOwn', 'forOwnRight'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should iterate over `length` properties', function(assert) {
+      assert.expect(1);
+
+      var object = { '0': 'zero', '1': 'one', 'length': 2 },
+          props = [];
+
+      func(object, function(value, prop) { props.push(prop); });
+      assert.deepEqual(props.sort(), ['0', '1', 'length']);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('iteration methods');
+
+  (function() {
+    var methods = [
+      '_baseEach',
+      'countBy',
+      'every',
+      'filter',
+      'find',
+      'findIndex',
+      'findKey',
+      'findLast',
+      'findLastIndex',
+      'findLastKey',
+      'forEach',
+      'forEachRight',
+      'forIn',
+      'forInRight',
+      'forOwn',
+      'forOwnRight',
+      'groupBy',
+      'keyBy',
+      'map',
+      'mapKeys',
+      'mapValues',
+      'maxBy',
+      'minBy',
+      'omitBy',
+      'partition',
+      'pickBy',
+      'reject',
+      'some'
+    ];
+
+    var arrayMethods = [
+      'findIndex',
+      'findLastIndex',
+      'maxBy',
+      'minBy'
+    ];
+
+    var collectionMethods = [
+      '_baseEach',
+      'countBy',
+      'every',
+      'filter',
+      'find',
+      'findLast',
+      'forEach',
+      'forEachRight',
+      'groupBy',
+      'keyBy',
+      'map',
+      'partition',
+      'reduce',
+      'reduceRight',
+      'reject',
+      'some'
+    ];
+
+    var forInMethods = [
+      'forIn',
+      'forInRight',
+      'omitBy',
+      'pickBy'
+    ];
+
+    var iterationMethods = [
+      '_baseEach',
+      'forEach',
+      'forEachRight',
+      'forIn',
+      'forInRight',
+      'forOwn',
+      'forOwnRight'
+    ];
+
+    var objectMethods = [
+      'findKey',
+      'findLastKey',
+      'forIn',
+      'forInRight',
+      'forOwn',
+      'forOwnRight',
+      'mapKeys',
+      'mapValues',
+      'omitBy',
+      'pickBy'
+    ];
+
+    var rightMethods = [
+      'findLast',
+      'findLastIndex',
+      'findLastKey',
+      'forEachRight',
+      'forInRight',
+      'forOwnRight'
+    ];
+
+    var unwrappedMethods = [
+      'each',
+      'eachRight',
+      'every',
+      'find',
+      'findIndex',
+      'findKey',
+      'findLast',
+      'findLastIndex',
+      'findLastKey',
+      'forEach',
+      'forEachRight',
+      'forIn',
+      'forInRight',
+      'forOwn',
+      'forOwnRight',
+      'max',
+      'maxBy',
+      'min',
+      'minBy',
+      'some'
+    ];
+
+    lodashStable.each(methods, function(methodName) {
+      var array = [1, 2, 3],
+          func = _[methodName],
+          isBy = /(^partition|By)$/.test(methodName),
+          isFind = /^find/.test(methodName),
+          isOmitPick = /^(?:omit|pick)By$/.test(methodName),
+          isSome = methodName == 'some';
+
+      QUnit.test('`_.' + methodName + '` should provide the correct iteratee arguments', function(assert) {
+        assert.expect(1);
+
+        if (func) {
+          var args,
+              expected = [1, 0, array];
+
+          func(array, function() {
+            args || (args = slice.call(arguments));
+          });
+
+          if (lodashStable.includes(rightMethods, methodName)) {
+            expected[0] = 3;
+            expected[1] = 2;
+          }
+          if (lodashStable.includes(objectMethods, methodName)) {
+            expected[1] += '';
+          }
+          if (isBy) {
+            expected.length = isOmitPick ? 2 : 1;
+          }
+          assert.deepEqual(args, expected);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should treat sparse arrays as dense', function(assert) {
+        assert.expect(1);
+
+        if (func) {
+          var array = [1];
+          array[2] = 3;
+
+          var expected = lodashStable.includes(objectMethods, methodName)
+            ? [[1, '0', array], [undefined, '1', array], [3, '2', array]]
+            : [[1,  0, array],  [undefined,  1,  array], [3,  2,  array]];
+
+          if (isBy) {
+            expected = lodashStable.map(expected, function(args) {
+              return args.slice(0, isOmitPick ? 2 : 1);
+            });
+          }
+          else if (lodashStable.includes(objectMethods, methodName)) {
+            expected = lodashStable.map(expected, function(args) {
+              args[1] += '';
+              return args;
+            });
+          }
+          if (lodashStable.includes(rightMethods, methodName)) {
+            expected.reverse();
+          }
+          var argsList = [];
+          func(array, function() {
+            argsList.push(slice.call(arguments));
+            return !(isFind || isSome);
+          });
+
+          assert.deepEqual(argsList, expected);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each(lodashStable.difference(methods, objectMethods), function(methodName) {
+      var array = [1, 2, 3],
+          func = _[methodName],
+          isEvery = methodName == 'every';
+
+      array.a = 1;
+
+      QUnit.test('`_.' + methodName + '` should not iterate custom properties on arrays', function(assert) {
+        assert.expect(1);
+
+        if (func) {
+          var keys = [];
+          func(array, function(value, key) {
+            keys.push(key);
+            return isEvery;
+          });
+
+          assert.notOk(lodashStable.includes(keys, 'a'));
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each(lodashStable.difference(methods, unwrappedMethods), function(methodName) {
+      var array = [1, 2, 3],
+          isBaseEach = methodName == '_baseEach';
+
+      QUnit.test('`_.' + methodName + '` should return a wrapped value when implicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!(isBaseEach || isNpm)) {
+          var wrapped = _(array)[methodName](noop);
+          assert.ok(wrapped instanceof _);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each(unwrappedMethods, function(methodName) {
+      var array = [1, 2, 3];
+
+      QUnit.test('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          var actual = _(array)[methodName](noop);
+          assert.notOk(actual instanceof _);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+        assert.expect(2);
+
+        if (!isNpm) {
+          var wrapped = _(array).chain(),
+              actual = wrapped[methodName](noop);
+
+          assert.ok(actual instanceof _);
+          assert.notStrictEqual(actual, wrapped);
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+    });
+
+    lodashStable.each(lodashStable.difference(methods, arrayMethods, forInMethods), function(methodName) {
+      var func = _[methodName];
+
+      QUnit.test('`_.' + methodName + '` iterates over own string keyed properties of objects', function(assert) {
+        assert.expect(1);
+
+        function Foo() {
+          this.a = 1;
+        }
+        Foo.prototype.b = 2;
+
+        if (func) {
+          var values = [];
+          func(new Foo, function(value) { values.push(value); });
+          assert.deepEqual(values, [1]);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each(iterationMethods, function(methodName) {
+      var array = [1, 2, 3],
+          func = _[methodName];
+
+      QUnit.test('`_.' + methodName + '` should return the collection', function(assert) {
+        assert.expect(1);
+
+        if (func) {
+          assert.strictEqual(func(array, Boolean), array);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each(collectionMethods, function(methodName) {
+      var func = _[methodName];
+
+      QUnit.test('`_.' + methodName + '` should use `isArrayLike` to determine whether a value is array-like', function(assert) {
+        assert.expect(3);
+
+        if (func) {
+          var isIteratedAsObject = function(object) {
+            var result = false;
+            func(object, function() { result = true; }, 0);
+            return result;
+          };
+
+          var values = [-1, '1', 1.1, Object(1), MAX_SAFE_INTEGER + 1],
+              expected = lodashStable.map(values, alwaysTrue);
+
+          var actual = lodashStable.map(values, function(length) {
+            return isIteratedAsObject({ 'length': length });
+          });
+
+          var Foo = function(a) {};
+          Foo.a = 1;
+
+          assert.deepEqual(actual, expected);
+          assert.ok(isIteratedAsObject(Foo));
+          assert.notOk(isIteratedAsObject({ 'length': 0 }));
+        }
+        else {
+          skipAssert(assert, 3);
+        }
+      });
+    });
+
+    lodashStable.each(methods, function(methodName) {
+      var func = _[methodName],
+          isFind = /^find/.test(methodName),
+          isSome = methodName == 'some',
+          isReduce = /^reduce/.test(methodName);
+
+      QUnit.test('`_.' + methodName + '` should ignore changes to `array.length`', function(assert) {
+        assert.expect(1);
+
+        if (func) {
+          var count = 0,
+              array = [1];
+
+          func(array, function() {
+            if (++count == 1) {
+              array.push(2);
+            }
+            return !(isFind || isSome);
+          }, isReduce ? array : null);
+
+          assert.strictEqual(count, 1);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each(lodashStable.difference(lodashStable.union(methods, collectionMethods), arrayMethods), function(methodName) {
+      var func = _[methodName],
+          isFind = /^find/.test(methodName),
+          isSome = methodName == 'some',
+          isReduce = /^reduce/.test(methodName);
+
+      QUnit.test('`_.' + methodName + '` should ignore added `object` properties', function(assert) {
+        assert.expect(1);
+
+        if (func) {
+          var count = 0,
+              object = { 'a': 1 };
+
+          func(object, function() {
+            if (++count == 1) {
+              object.b = 2;
+            }
+            return !(isFind || isSome);
+          }, isReduce ? object : null);
+
+          assert.strictEqual(count, 1);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('object assignments');
+
+  lodashStable.each(['assign', 'assignIn', 'defaults', 'merge'], function(methodName) {
+    var func = _[methodName],
+        isAssign = methodName == 'assign',
+        isDefaults = methodName == 'defaults';
+
+    QUnit.test('`_.' + methodName + '` should coerce primitives to objects', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(primitives, function(value) {
+        var object = Object(value);
+        object.a = 1;
+        return object;
+      });
+
+      var actual = lodashStable.map(primitives, function(value) {
+        return func(value, { 'a': 1 });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should assign own ' + (isAssign ? '' : 'and inherited ') + 'string keyed source properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var expected = isAssign ? { 'a': 1 } : { 'a': 1, 'b': 2 };
+      assert.deepEqual(func({}, new Foo), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not error on nullish sources', function(assert) {
+      assert.expect(1);
+
+      try {
+        assert.deepEqual(func({ 'a': 1 }, undefined, { 'b': 2 }, null), { 'a': 1, 'b': 2 });
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should create an object when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var source = { 'a': 1 },
+          values = [null, undefined],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        var object = func(value, source);
+        return object !== source && lodashStable.isEqual(object, source);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      actual = lodashStable.map(values, function(value) {
+        return lodashStable.isEqual(func(value), {});
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work as an iteratee for methods like `_.reduce`', function(assert) {
+      assert.expect(2);
+
+      var array = [{ 'a': 1 }, { 'b': 2 }, { 'c': 3 }],
+          expected = { 'a': isDefaults ? 0 : 1, 'b': 2, 'c': 3 };
+
+      assert.deepEqual(lodashStable.reduce(array, func, { 'a': 0 }), expected);
+
+      var fn = function() {};
+      fn.a = array[0];
+      fn.b = array[1];
+      fn.c = array[2];
+
+      assert.deepEqual(_.reduce(fn, func, { 'a': 0 }), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not return the existing wrapped value when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _({ 'a': 1 }),
+            actual = wrapped[methodName]({ 'b': 2 });
+
+        assert.notStrictEqual(actual, wrapped);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  lodashStable.each(['assign', 'assignIn', 'merge'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should not treat `object` as `source`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype.a = 1;
+
+      var actual = func(new Foo, { 'b': 2 });
+      assert.notOk(_.has(actual, 'a'));
+    });
+  });
+
+  lodashStable.each(['assign', 'assignIn', 'assignInWith', 'assignWith', 'defaults', 'merge', 'mergeWith'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should not assign values that are the same as their destinations', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a', ['a'], { 'a': 1 }, NaN], function(value) {
+        if (defineProperty) {
+          var object = {},
+              pass = true;
+
+          defineProperty(object, 'a', {
+            'enumerable': true,
+            'configurable': true,
+            'get': lodashStable.constant(value),
+            'set': function() { pass = false; }
+          });
+
+          func(object, { 'a': value });
+          assert.ok(pass);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  });
+
+  lodashStable.each(['assignWith', 'assignInWith', 'mergeWith'], function(methodName) {
+    var func = _[methodName],
+        isMergeWith = methodName == 'mergeWith';
+
+    QUnit.test('`_.' + methodName + '` should provide the correct `customizer` arguments', function(assert) {
+      assert.expect(3);
+
+      var args,
+          object = { 'a': 1 },
+          source = { 'a': 2 },
+          expected = lodashStable.map([1, 2, 'a', object, source], lodashStable.cloneDeep);
+
+      func(object, source, function() {
+        args || (args = lodashStable.map(slice.call(arguments, 0, 5), lodashStable.cloneDeep));
+      });
+
+      assert.deepEqual(args, expected, 'primitive property values');
+
+      args = undefined;
+      object = { 'a': 1 };
+      source = { 'b': 2 };
+      expected = lodashStable.map([undefined, 2, 'b', object, source], lodashStable.cloneDeep);
+
+      func(object, source, function() {
+        args || (args = lodashStable.map(slice.call(arguments, 0, 5), lodashStable.cloneDeep));
+      });
+
+      assert.deepEqual(args, expected, 'missing destination property');
+
+      var argsList = [],
+          objectValue = [1, 2],
+          sourceValue = { 'b': 2 };
+
+      object = { 'a': objectValue };
+      source = { 'a': sourceValue };
+      expected = [lodashStable.map([objectValue, sourceValue, 'a', object, source], lodashStable.cloneDeep)];
+
+      if (isMergeWith) {
+        expected.push(lodashStable.map([undefined, 2, 'b', objectValue, sourceValue], lodashStable.cloneDeep));
+      }
+      func(object, source, function() {
+        argsList.push(lodashStable.map(slice.call(arguments, 0, 5), lodashStable.cloneDeep));
+      });
+
+      assert.deepEqual(argsList, expected, 'object property values');
+    });
+
+    QUnit.test('`_.' + methodName + '` should not treat the second argument as a `customizer` callback', function(assert) {
+      assert.expect(2);
+
+      function callback() {}
+      callback.b = 2;
+
+      var actual = func({ 'a': 1 }, callback);
+      assert.deepEqual(actual, { 'a': 1, 'b': 2 });
+
+      actual = func({ 'a': 1 }, callback, { 'c': 3 });
+      assert.deepEqual(actual, { 'a': 1, 'b': 2, 'c': 3 });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('exit early');
+
+  lodashStable.each(['_baseEach', 'forEach', 'forEachRight', 'forIn', 'forInRight', 'forOwn', 'forOwnRight', 'transform'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` can exit early when iterating arrays', function(assert) {
+      assert.expect(1);
+
+      if (func) {
+        var array = [1, 2, 3],
+            values = [];
+
+        func(array, function(value, other) {
+          values.push(lodashStable.isArray(value) ? other : value);
+          return false;
+        });
+
+        assert.deepEqual(values, [lodashStable.endsWith(methodName, 'Right') ? 3 : 1]);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` can exit early when iterating objects', function(assert) {
+      assert.expect(1);
+
+      if (func) {
+        var object = { 'a': 1, 'b': 2, 'c': 3 },
+            values = [];
+
+        func(object, function(value, other) {
+          values.push(lodashStable.isArray(value) ? other : value);
+          return false;
+        });
+
+        assert.strictEqual(values.length, 1);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('`__proto__` property bugs');
+
+  (function() {
+    QUnit.test('internal data objects should work with the `__proto__` key', function(assert) {
+      assert.expect(4);
+
+      var stringLiteral = '__proto__',
+          stringObject = Object(stringLiteral),
+          expected = [stringLiteral, stringObject];
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function(count) {
+        return isEven(count) ? stringLiteral : stringObject;
+      });
+
+      assert.deepEqual(_.difference(largeArray, largeArray), []);
+      assert.deepEqual(_.intersection(largeArray, largeArray), expected);
+      assert.deepEqual(_.uniq(largeArray), expected);
+      assert.deepEqual(_.without.apply(_, [largeArray].concat(largeArray)), []);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.fromPairs');
+
+  (function() {
+    QUnit.test('should accept a two dimensional array', function(assert) {
+      assert.expect(1);
+
+      var array = [['a', 1], ['b', 2]],
+          object = { 'a': 1, 'b': 2 },
+          actual = _.fromPairs(array);
+
+      assert.deepEqual(actual, object);
+    });
+
+    QUnit.test('should accept a falsey `array` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyObject);
+
+      var actual = lodashStable.map(falsey, function(array, index) {
+        try {
+          return index ? _.fromPairs(array) : _.fromPairs();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not support deep paths', function(assert) {
+      assert.expect(1);
+
+      var actual = _.fromPairs([['a.b', 1]]);
+      assert.deepEqual(actual, { 'a.b': 1 });
+    });
+
+    QUnit.test('should support consuming the return value of `_.toPairs`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a.b': 1 };
+      assert.deepEqual(_.fromPairs(_.toPairs(object)), object);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.times(LARGE_ARRAY_SIZE, function(index) {
+          return ['key' + index, index];
+        });
+
+        var actual = _(array).fromPairs().map(square).filter(isEven).take().value();
+
+        assert.deepEqual(actual, _.take(_.filter(_.map(_.fromPairs(array), square), isEven)));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.functions');
+
+  (function() {
+    QUnit.test('should return the function names of an object', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 'a', 'b': identity, 'c': /x/, 'd': noop },
+          actual = _.functions(object).sort();
+
+      assert.deepEqual(actual, ['b', 'd']);
+    });
+
+    QUnit.test('should not include inherited functions', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = identity;
+        this.b = 'b';
+      }
+      Foo.prototype.c = noop;
+
+      assert.deepEqual(_.functions(new Foo), ['a']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.groupBy');
+
+  (function() {
+    var array = [6.1, 4.2, 6.3];
+
+    QUnit.test('should transform keys by `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.groupBy(array, Math.floor);
+      assert.deepEqual(actual, { '4': [4.2], '6': [6.1, 6.3] });
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var array = [6, 4, 6],
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant({ '4': [4], '6':  [6, 6] }));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.groupBy(array, value) : _.groupBy(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var actual = _.groupBy(['one', 'two', 'three'], 'length');
+      assert.deepEqual(actual, { '3': ['one', 'two'], '5': ['three'] });
+    });
+
+    QUnit.test('should only add values to own, not inherited, properties', function(assert) {
+      assert.expect(2);
+
+      var actual = _.groupBy(array, function(n) {
+        return Math.floor(n) > 4 ? 'hasOwnProperty' : 'constructor';
+      });
+
+      assert.deepEqual(actual.constructor, [4.2]);
+      assert.deepEqual(actual.hasOwnProperty, [6.1, 6.3]);
+    });
+
+    QUnit.test('should work with a number for `iteratee`', function(assert) {
+      assert.expect(2);
+
+      var array = [
+        [1, 'a'],
+        [2, 'a'],
+        [2, 'b']
+      ];
+
+      assert.deepEqual(_.groupBy(array, 0), { '1': [[1, 'a']], '2': [[2, 'a'], [2, 'b']] });
+      assert.deepEqual(_.groupBy(array, 1), { 'a': [[1, 'a'], [2, 'a']], 'b': [[2, 'b']] });
+    });
+
+    QUnit.test('should work with an object for `collection`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.groupBy({ 'a': 6.1, 'b': 4.2, 'c': 6.3 }, Math.floor);
+      assert.deepEqual(actual, { '4': [4.2], '6': [6.1, 6.3] });
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE).concat(
+          lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 2), LARGE_ARRAY_SIZE),
+          lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 1.5), LARGE_ARRAY_SIZE)
+        );
+
+        var iteratee = function(value) { value.push(value[0]); return value; },
+            predicate = function(value) { return isEven(value[0]); },
+            actual = _(array).groupBy().map(iteratee).filter(predicate).take().value();
+
+        assert.deepEqual(actual, _.take(_.filter(lodashStable.map(_.groupBy(array), iteratee), predicate)));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.gt');
+
+  (function() {
+    QUnit.test('should return `true` if `value` > `other`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.gt(3, 1), true);
+      assert.strictEqual(_.gt('def', 'abc'), true);
+    });
+
+    QUnit.test('should return `false` if `value` is <= `other`', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.gt(1, 3), false);
+      assert.strictEqual(_.gt(3, 3), false);
+      assert.strictEqual(_.gt('abc', 'def'), false);
+      assert.strictEqual(_.gt('def', 'def'), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.gte');
+
+  (function() {
+    QUnit.test('should return `true` if `value` >= `other`', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.gte(3, 1), true);
+      assert.strictEqual(_.gte(3, 3), true);
+      assert.strictEqual(_.gte('def', 'abc'), true);
+      assert.strictEqual(_.gte('def', 'def'), true);
+    });
+
+    QUnit.test('should return `false` if `value` is less than `other`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.gte(1, 3), false);
+      assert.strictEqual(_.gte('abc', 'def'), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('has methods');
+
+  lodashStable.each(['has', 'hasIn'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        func = _[methodName],
+        isHas = methodName == 'has';
+
+    QUnit.test('`_.' + methodName + '` should check for own properties', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1 };
+
+      lodashStable.each(['a', ['a']], function(path) {
+        assert.strictEqual(func(object, path), true);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not use the `hasOwnProperty` method of the object', function(assert) {
+      assert.expect(1);
+
+      var object = { 'hasOwnProperty': null, 'a': 1 };
+      assert.strictEqual(func(object, 'a'), true);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support deep paths', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(func(object, path), true);
+      });
+
+      lodashStable.each(['a.a', ['a', 'a']], function(path) {
+        assert.strictEqual(func(object, path), false);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `path` to a string', function(assert) {
+      assert.expect(1);
+
+      function fn() {}
+      fn.toString = lodashStable.constant('fn');
+
+      var expected = [1, 1, 2, 2, 3, 3, 4, 4],
+          objects = [{ 'null': 1 }, { 'undefined': 2 }, { 'fn': 3 }, { '[object Object]': 4 }],
+          values = [null, undefined, fn, {}];
+
+      var actual = lodashStable.transform(objects, function(result, object, index) {
+        var key = values[index];
+        lodashStable.each([key, [key]], function(path) {
+          var prop = _.property(key);
+          result.push(prop(object));
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func(args, 1), true);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a non-string `path`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3];
+
+      lodashStable.each([1, [1]], function(path) {
+        assert.strictEqual(func(array, path), true);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': 'a', '0': 'b' },
+          props = [-0, Object(-0), 0, Object(0)],
+          expected = lodashStable.map(props, alwaysTrue);
+
+      var actual = lodashStable.map(props, function(key) {
+        return func(object, key);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a symbol `path`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this[symbol] = 1;
+      }
+
+      if (Symbol) {
+        var symbol2 = Symbol('b');
+        Foo.prototype[symbol2] = 2;
+        var path = isHas ? symbol : symbol2;
+
+        assert.strictEqual(func(new Foo, path), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should work for objects with a `[[Prototype]]` of `null`', function(assert) {
+      assert.expect(1);
+
+      if (create)  {
+        var object = create(null);
+        object[1] = 'a';
+        assert.strictEqual(func(object, 1), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should check for a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': 1 };
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        assert.strictEqual(func(object, path), true);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `' + (isHas ? 'false' : 'true') + '` for inherited properties', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.a = 1;
+
+      lodashStable.each(['a', ['a']], function(path) {
+        assert.strictEqual(func(new Foo, path), !isHas);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `' + (isHas ? 'false' : 'true') + '` for nested inherited properties', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.a = { 'b': 1 };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(func(new Foo, path), !isHas);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `true` for index values within bounds for arrays, `arguments` objects, and strings', function(assert) {
+      assert.expect(2);
+
+      var string = Object('abc');
+      delete args[0];
+      delete string[0];
+
+      var values = [Array(3), args, string],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(value, 0);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(values, lodashStable.constant([true, true]));
+
+      actual = lodashStable.map(values, function(value) {
+        return lodashStable.map(['a[0]', ['a', '0']], function(path) {
+          return func({ 'a': value }, path);
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+      args[0] = 1;
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `false` when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        var actual = lodashStable.map(values, function(value) {
+          return func(value, path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `false` for deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) {
+        var actual = lodashStable.map(values, function(value) {
+          return func(value, path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `false` for nullish values of nested objects', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var actual = lodashStable.map(values, function(value, index) {
+          var object = index ? { 'a': value } : {};
+          return func(object, path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.head');
+
+  (function() {
+    var array = [1, 2, 3, 4];
+
+    QUnit.test('should return the first element', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.head(array), 1);
+    });
+
+    QUnit.test('should return `undefined` when querying empty arrays', function(assert) {
+      assert.expect(1);
+
+      arrayProto[0] = 1;
+      assert.strictEqual(_.head([]), undefined);
+      arrayProto.length = 0;
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.head);
+
+      assert.deepEqual(actual, [1, 4, 7]);
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_(array).head(), 1);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(array).chain().head() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should not execute immediately when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _(array).chain().head();
+        assert.strictEqual(wrapped.__wrapped__, array);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var largeArray = lodashStable.range(LARGE_ARRAY_SIZE),
+            smallArray = array;
+
+        lodashStable.times(2, function(index) {
+          var array = index ? largeArray : smallArray,
+              wrapped = _(array).filter(isEven);
+
+          assert.strictEqual(wrapped.head(), _.head(_.filter(array, isEven)));
+        });
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.first, _.head);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.identity');
+
+  (function() {
+    QUnit.test('should return the first argument given', function(assert) {
+      assert.expect(1);
+
+      var object = { 'name': 'fred' };
+      assert.strictEqual(_.identity(object), object);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.includes');
+
+  (function() {
+    lodashStable.each({
+      'an `arguments` object': arguments,
+      'an array': [1, 2, 3, 4],
+      'an object': { 'a': 1, 'b': 2, 'c': 3, 'd': 4 },
+      'a string': '1234'
+    },
+    function(collection, key) {
+      var isStr = typeof collection == 'string',
+          values = _.toArray(collection),
+          length = values.length;
+
+      QUnit.test('should work with ' + key + ' and  return `true` for  matched values', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(_.includes(collection, 3), true);
+      });
+
+      QUnit.test('should work with ' + key + ' and  return `false` for unmatched values', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(_.includes(collection, 5), false);
+      });
+
+      QUnit.test('should work with ' + key + ' and a positive `fromIndex`', function(assert) {
+        assert.expect(2);
+
+        assert.strictEqual(_.includes(collection, values[2], 2), true);
+        assert.strictEqual(_.includes(collection, values[1], 2), false);
+      });
+
+      QUnit.test('should work with ' + key + ' and a `fromIndex` >= `collection.length`', function(assert) {
+        assert.expect(12);
+
+        lodashStable.each([4, 6, Math.pow(2, 32), Infinity], function(fromIndex) {
+          assert.strictEqual(_.includes(collection, 1, fromIndex), false);
+          assert.strictEqual(_.includes(collection, undefined, fromIndex), false);
+          assert.strictEqual(_.includes(collection, '', fromIndex), (isStr && fromIndex == length));
+        });
+      });
+
+      QUnit.test('should work with ' + key + ' and treat falsey `fromIndex` values as `0`', function(assert) {
+        assert.expect(1);
+
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(fromIndex) {
+          return _.includes(collection, values[0], fromIndex);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+
+      QUnit.test('should work with ' + key + ' and coerce non-integer `fromIndex` values to integers', function(assert) {
+        assert.expect(3);
+
+        assert.strictEqual(_.includes(collection, values[0], '1'), false);
+        assert.strictEqual(_.includes(collection, values[0], 0.1), true);
+        assert.strictEqual(_.includes(collection, values[0], NaN), true);
+      });
+
+      QUnit.test('should work with ' + key + ' and a negative `fromIndex`', function(assert) {
+        assert.expect(2);
+
+        assert.strictEqual(_.includes(collection, values[2], -2), true);
+        assert.strictEqual(_.includes(collection, values[1], -2), false);
+      });
+
+      QUnit.test('should work with ' + key + ' and a negative `fromIndex` <= negative `collection.length`', function(assert) {
+        assert.expect(3);
+
+        lodashStable.each([-4, -6, -Infinity], function(fromIndex) {
+          assert.strictEqual(_.includes(collection, values[0], fromIndex), true);
+        });
+      });
+
+      QUnit.test('should work with ' + key + ' and floor `position` values', function(assert) {
+        assert.expect(1);
+
+        assert.strictEqual(_.includes(collection, 2, 1.2), true);
+      });
+
+      QUnit.test('should work with ' + key + ' and return an unwrapped value implicitly when chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          assert.strictEqual(_(collection).includes(3), true);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('should work with ' + key + ' and return a wrapped value when explicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          assert.ok(_(collection).chain().includes(3) instanceof _);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    lodashStable.each({
+      'literal': 'abc',
+      'object': Object('abc')
+    },
+    function(collection, key) {
+      QUnit.test('should work with a string ' + key + ' for `collection`', function(assert) {
+        assert.expect(2);
+
+        assert.strictEqual(_.includes(collection, 'bc'), true);
+        assert.strictEqual(_.includes(collection, 'd'), false);
+      });
+    });
+
+    QUnit.test('should return `false` for empty collections', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, alwaysFalse);
+
+      var actual = lodashStable.map(empties, function(value) {
+        try {
+          return _.includes(value);
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should match `NaN`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.includes([1, NaN, 3], NaN), true);
+    });
+
+    QUnit.test('should match `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.includes([-0], 0), true);
+      assert.strictEqual(_.includes([0], -0), true);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.every`', function(assert) {
+      assert.expect(1);
+
+      var array1 = [1, 2, 3],
+          array2 = [2, 3, 1];
+
+      assert.ok(lodashStable.every(array1, lodashStable.partial(_.includes, array2)));
+    });
+  }(1, 2, 3, 4));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.indexOf');
+
+  (function() {
+    var array = [1, 2, 3, 1, 2, 3];
+
+    QUnit.test('should return the index of the first matched value', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.indexOf(array, 3), 2);
+    });
+
+    QUnit.test('should work with a positive `fromIndex`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.indexOf(array, 1, 2), 3);
+    });
+
+    QUnit.test('should work with `fromIndex` >= `array.length`', function(assert) {
+      assert.expect(1);
+
+      var values = [6, 8, Math.pow(2, 32), Infinity],
+          expected = lodashStable.map(values, lodashStable.constant([-1, -1, -1]));
+
+      var actual = lodashStable.map(values, function(fromIndex) {
+        return [
+          _.indexOf(array, undefined, fromIndex),
+          _.indexOf(array, 1, fromIndex),
+          _.indexOf(array, '', fromIndex)
+        ];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with a negative `fromIndex`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.indexOf(array, 2, -3), 4);
+    });
+
+    QUnit.test('should work with a negative `fromIndex` <= `-array.length`', function(assert) {
+      assert.expect(1);
+
+      var values = [-6, -8, -Infinity],
+          expected = lodashStable.map(values, alwaysZero);
+
+      var actual = lodashStable.map(values, function(fromIndex) {
+        return _.indexOf(array, 1, fromIndex);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat falsey `fromIndex` values as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysZero);
+
+      var actual = lodashStable.map(falsey, function(fromIndex) {
+        return _.indexOf(array, 1, fromIndex);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce `fromIndex` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.indexOf(array, 2, 1.2), 1);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.initial');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should accept a falsey `array` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyArray);
+
+      var actual = lodashStable.map(falsey, function(array, index) {
+        try {
+          return index ? _.initial(array) : _.initial();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should exclude last element', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.initial(array), [1, 2]);
+    });
+
+    QUnit.test('should return an empty when querying empty arrays', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.initial([]), []);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.initial);
+
+      assert.deepEqual(actual, [[1, 2], [4, 5], [7, 8]]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            values = [];
+
+        var actual = _(array).initial().filter(function(value) {
+          values.push(value);
+          return false;
+        })
+        .value();
+
+        assert.deepEqual(actual, []);
+        assert.deepEqual(values, _.initial(array));
+
+        values = [];
+
+        actual = _(array).filter(function(value) {
+          values.push(value);
+          return isEven(value);
+        })
+        .initial()
+        .value();
+
+        assert.deepEqual(actual, _.initial(lodashStable.filter(array, isEven)));
+        assert.deepEqual(values, array);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.inRange');
+
+  (function() {
+    QUnit.test('should work with an `end` argument', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.inRange(3, 5), true);
+      assert.strictEqual(_.inRange(5, 5), false);
+      assert.strictEqual(_.inRange(6, 5), false);
+    });
+
+    QUnit.test('should work with `start` and `end` arguments', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.inRange(1, 1, 5), true);
+      assert.strictEqual(_.inRange(3, 1, 5), true);
+      assert.strictEqual(_.inRange(0, 1, 5), false);
+      assert.strictEqual(_.inRange(5, 1, 5), false);
+    });
+
+    QUnit.test('should treat falsey `start` arguments as `0`', function(assert) {
+      assert.expect(13);
+
+      lodashStable.each(falsey, function(value, index) {
+        if (index) {
+          assert.strictEqual(_.inRange(0, value), false);
+          assert.strictEqual(_.inRange(0, value, 1), true);
+        } else {
+          assert.strictEqual(_.inRange(0), false);
+        }
+      });
+    });
+
+    QUnit.test('should swap `start` and `end` when `start` > `end`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.inRange(2, 5, 1), true);
+      assert.strictEqual(_.inRange(-3, -2, -6), true);
+    });
+
+    QUnit.test('should work with a floating point `n` value', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.inRange(0.5, 5), true);
+      assert.strictEqual(_.inRange(1.2, 1, 5), true);
+      assert.strictEqual(_.inRange(5.2, 5), false);
+      assert.strictEqual(_.inRange(0.5, 1, 5), false);
+    });
+
+    QUnit.test('should coerce arguments to finite numbers', function(assert) {
+      assert.expect(1);
+
+      var actual = [_.inRange(0, '0', 1), _.inRange(0, '1'), _.inRange(0, 0, '1'), _.inRange(0, NaN, 1), _.inRange(-1, -1, NaN)],
+          expected = lodashStable.map(actual, alwaysTrue);
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('intersection methods');
+
+  lodashStable.each(['intersection', 'intersectionBy', 'intersectionWith'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return the intersection of two arrays', function(assert) {
+      assert.expect(2);
+
+      var actual = func([1, 3, 2], [5, 2, 1, 4]);
+      assert.deepEqual(actual, [1, 2]);
+
+      actual = func([5, 2, 1, 4], [1, 3, 2]);
+      assert.deepEqual(actual, [2, 1]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return the intersection of multiple arrays', function(assert) {
+      assert.expect(2);
+
+      var actual = func([1, 3, 2], [5, 2, 1, 4], [2, 1]);
+      assert.deepEqual(actual, [1, 2]);
+
+      actual = func([5, 2, 1, 4], [2, 1], [1, 3, 2]);
+      assert.deepEqual(actual, [2, 1]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an array of unique values', function(assert) {
+      assert.expect(1);
+
+      var actual = func([1, 1, 3, 2, 2], [5, 2, 2, 1, 4], [2, 1, 1]);
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a single array', function(assert) {
+      assert.expect(1);
+
+      var actual = func([1, 1, 3, 2, 2]);
+      assert.deepEqual(actual, [1, 3, 2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `arguments` objects', function(assert) {
+      assert.expect(2);
+
+      var array = [0, 1, null, 3],
+          expected = [1, 3];
+
+      assert.deepEqual(func(array, args), expected);
+      assert.deepEqual(func(args, array), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat `-0` as `0`', function(assert) {
+      assert.expect(1);
+
+      var values = [-0, 0],
+          expected = lodashStable.map(values, lodashStable.constant(['0']));
+
+      var actual = lodashStable.map(values, function(value) {
+        return lodashStable.map(func(values, [value]), lodashStable.toString);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should match `NaN`', function(assert) {
+      assert.expect(1);
+
+      var actual = func([1, NaN, 3], [NaN, 5, NaN]);
+      assert.deepEqual(actual, [NaN]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of `-0` as `0`', function(assert) {
+      assert.expect(1);
+
+      var values = [-0, 0],
+          expected = lodashStable.map(values, lodashStable.constant(['0']));
+
+      var actual = lodashStable.map(values, function(value) {
+        var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(value));
+        return lodashStable.map(func(values, largeArray), lodashStable.toString);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of `NaN`', function(assert) {
+      assert.expect(1);
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, alwaysNaN);
+      assert.deepEqual(func([1, NaN, 3], largeArray), [NaN]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of objects', function(assert) {
+      assert.expect(2);
+
+      var object = {},
+          largeArray = lodashStable.times(LARGE_ARRAY_SIZE, lodashStable.constant(object));
+
+      assert.deepEqual(func([object], largeArray), [object]);
+      assert.deepEqual(func(lodashStable.range(LARGE_ARRAY_SIZE), [1]), [1]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat values that are not arrays or `arguments` objects as empty', function(assert) {
+      assert.expect(3);
+
+      var array = [0, 1, null, 3];
+      assert.deepEqual(func(array, 3, { '0': 1 }, null), []);
+      assert.deepEqual(func(null, array, null, [2, 3]), []);
+      assert.deepEqual(func(array, null, args, null), []);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _([1, 3, 2])[methodName]([5, 2, 1, 4]);
+        assert.ok(wrapped instanceof _);
+        assert.deepEqual(wrapped.value(), [1, 2]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.intersectionBy');
+
+  (function() {
+    QUnit.test('should accept an `iteratee` argument', function(assert) {
+      assert.expect(2);
+
+      var actual = _.intersectionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+      assert.deepEqual(actual, [2.1]);
+
+      actual = _.intersectionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+      assert.deepEqual(actual, [{ 'x': 1 }]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.intersectionBy([2.1, 1.2], [4.3, 2.4], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [4.3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.intersectionWith');
+
+  (function() {
+    QUnit.test('should work with a `comparator` argument', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }],
+          others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }],
+          actual = _.intersectionWith(objects, others, lodashStable.isEqual);
+
+      assert.deepEqual(actual, [objects[0]]);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var array = [-0],
+          largeArray = lodashStable.times(LARGE_ARRAY_SIZE, alwaysZero),
+          others = [[0], largeArray],
+          expected = lodashStable.map(others, lodashStable.constant(['-0']));
+
+      var actual = lodashStable.map(others, function(other) {
+        return lodashStable.map(_.intersectionWith(array, other, lodashStable.eq), lodashStable.toString);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.invert');
+
+  (function() {
+    QUnit.test('should invert an object', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1, 'b': 2 },
+          actual = _.invert(object);
+
+      assert.deepEqual(actual, { '1': 'a', '2': 'b' });
+      assert.deepEqual(_.invert(actual), { 'a': '1', 'b': '2' });
+    });
+
+    QUnit.test('should work with values that shadow keys on `Object.prototype`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 'hasOwnProperty', 'b': 'constructor' };
+      assert.deepEqual(_.invert(object), { 'hasOwnProperty': 'a', 'constructor': 'b' });
+    });
+
+    QUnit.test('should work with an object that has a `length` property', function(assert) {
+      assert.expect(1);
+
+      var object = { '0': 'a', '1': 'b', 'length': 2 };
+      assert.deepEqual(_.invert(object), { 'a': '0', 'b': '1', '2': 'length' });
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var object = { 'a': 1, 'b': 2 },
+            wrapped = _(object).invert();
+
+        assert.ok(wrapped instanceof _);
+        assert.deepEqual(wrapped.value(), { '1': 'a', '2': 'b' });
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.invertBy');
+
+  (function() {
+    var object = { 'a': 1, 'b': 2, 'c': 1 };
+
+    QUnit.test('should transform keys by `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'group1': ['a', 'c'], 'group2': ['b'] };
+
+      var actual = _.invertBy(object, function(value) {
+        return 'group' + value;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant({ '1': ['a', 'c'], '2': ['b'] }));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.invertBy(object, value) : _.invertBy(object);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should only add multiple values to own, not inherited, properties', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 'hasOwnProperty', 'b': 'constructor' },
+          expected = { 'hasOwnProperty': ['a'], 'constructor': ['b'] };
+
+      assert.ok(lodashStable.isEqual(_.invertBy(object), expected));
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _(object).invertBy();
+
+        assert.ok(wrapped instanceof _);
+        assert.deepEqual(wrapped.value(), { '1': ['a', 'c'], '2': ['b'] });
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.invoke');
+
+  (function() {
+    QUnit.test('should invoke a method on `object`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': lodashStable.constant('A') },
+          actual = _.invoke(object, 'a');
+
+      assert.strictEqual(actual, 'A');
+    });
+
+    QUnit.test('should support invoking with arguments', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': function(a, b) { return [a, b]; } },
+          actual = _.invoke(object, 'a', 1, 2);
+
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('should not error on nullish elements', function(assert) {
+      assert.expect(1);
+
+      var values = [null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      var actual = lodashStable.map(values, function(value) {
+        try {
+          return _.invoke(value, 'a.b', 1, 2);
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': alwaysA, '0': alwaysB },
+          props = [-0, Object(-0), 0, Object(0)];
+
+      var actual = lodashStable.map(props, function(key) {
+        return _.invoke(object, key);
+      });
+
+      assert.deepEqual(actual, ['a', 'a', 'b', 'b']);
+    });
+
+    QUnit.test('should support deep paths', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': function(a, b) { return [a, b]; } } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var actual = _.invoke(object, path, 1, 2);
+        assert.deepEqual(actual, [1, 2]);
+      });
+    });
+
+    QUnit.test('should invoke deep property methods with the correct `this` binding', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.deepEqual(_.invoke(object, path), 1);
+      });
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var object = { 'a': alwaysOne };
+        assert.strictEqual(_(object).invoke('a'), 1);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var object = { 'a': alwaysOne };
+        assert.ok(_(object).chain().invoke('a') instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.invokeMap');
+
+  (function() {
+    QUnit.test('should invoke a methods on each element of `collection`', function(assert) {
+      assert.expect(1);
+
+      var array = ['a', 'b', 'c'],
+          actual = _.invokeMap(array, 'toUpperCase');
+
+      assert.deepEqual(actual, ['A', 'B', 'C']);
+    });
+
+    QUnit.test('should support invoking with arguments', function(assert) {
+      assert.expect(1);
+
+      var array = [function() { return slice.call(arguments); }],
+          actual = _.invokeMap(array, 'call', null, 'a', 'b', 'c');
+
+      assert.deepEqual(actual, [['a', 'b', 'c']]);
+    });
+
+    QUnit.test('should work with a function for `methodName`', function(assert) {
+      assert.expect(1);
+
+      var array = ['a', 'b', 'c'];
+
+      var actual = _.invokeMap(array, function(left, right) {
+        return left + this.toUpperCase() + right;
+      }, '(', ')');
+
+      assert.deepEqual(actual, ['(A)', '(B)', '(C)']);
+    });
+
+    QUnit.test('should work with an object for `collection`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          actual = _.invokeMap(object, 'toFixed', 1);
+
+      assert.deepEqual(actual, ['1.0', '2.0', '3.0']);
+    });
+
+    QUnit.test('should treat number values for `collection` as empty', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.invokeMap(1), []);
+    });
+
+    QUnit.test('should not error on nullish elements', function(assert) {
+      assert.expect(1);
+
+      var array = ['a', null, undefined, 'd'];
+
+      try {
+        var actual = _.invokeMap(array, 'toUpperCase');
+      } catch (e) {}
+
+      assert.deepEqual(actual, ['A', undefined, undefined, 'D']);
+    });
+
+    QUnit.test('should not error on elements with missing properties', function(assert) {
+      assert.expect(1);
+
+      var objects = lodashStable.map([null, undefined, alwaysOne], function(value) {
+        return { 'a': value };
+      });
+
+      var expected = lodashStable.map(objects, function(object) {
+        return object.a ? object.a() : undefined;
+      });
+
+      try {
+        var actual = _.invokeMap(objects, 'a');
+      } catch (e) {}
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should invoke deep property methods with the correct `this` binding', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.deepEqual(_.invokeMap([object], path), [1]);
+      });
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var array = ['a', 'b', 'c'],
+            wrapped = _(array),
+            actual = wrapped.invokeMap('toUpperCase');
+
+        assert.ok(actual instanceof _);
+        assert.deepEqual(actual.valueOf(), ['A', 'B', 'C']);
+
+        actual = wrapped.invokeMap(function(left, right) {
+          return left + this.toUpperCase() + right;
+        }, '(', ')');
+
+        assert.ok(actual instanceof _);
+        assert.deepEqual(actual.valueOf(), ['(A)', '(B)', '(C)']);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should support shortcut fusion', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var count = 0,
+            method = function() { count++; return this.index; };
+
+        var array = lodashStable.times(LARGE_ARRAY_SIZE, function(index) {
+          return { 'index': index, 'method': method };
+        });
+
+        var actual = _(array).invokeMap('method').take(1).value();
+
+        assert.strictEqual(count, 1);
+        assert.deepEqual(actual, [0]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isArguments');
+
+  (function() {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        strictArgs = (function() { 'use strict'; return arguments; }(1, 2, 3));
+
+    QUnit.test('should return `true` for `arguments` objects', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isArguments(args), true);
+      assert.strictEqual(_.isArguments(strictArgs), true);
+    });
+
+    QUnit.test('should return `false` for non `arguments` objects', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isArguments(value) : _.isArguments();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isArguments([1, 2, 3]), false);
+      assert.strictEqual(_.isArguments(true), false);
+      assert.strictEqual(_.isArguments(new Date), false);
+      assert.strictEqual(_.isArguments(new Error), false);
+      assert.strictEqual(_.isArguments(_), false);
+      assert.strictEqual(_.isArguments(slice), false);
+      assert.strictEqual(_.isArguments({ '0': 1, 'callee': noop, 'length': 1 }), false);
+      assert.strictEqual(_.isArguments(1), false);
+      assert.strictEqual(_.isArguments(/x/), false);
+      assert.strictEqual(_.isArguments('a'), false);
+      assert.strictEqual(_.isArguments(symbol), false);
+    });
+
+    QUnit.test('should work with an `arguments` object from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.arguments) {
+        assert.strictEqual(_.isArguments(realm.arguments), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isArray');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for arrays', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isArray([1, 2, 3]), true);
+    });
+
+    QUnit.test('should return `false` for non-arrays', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isArray(value) : _.isArray();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isArray(args), false);
+      assert.strictEqual(_.isArray(true), false);
+      assert.strictEqual(_.isArray(new Date), false);
+      assert.strictEqual(_.isArray(new Error), false);
+      assert.strictEqual(_.isArray(_), false);
+      assert.strictEqual(_.isArray(slice), false);
+      assert.strictEqual(_.isArray({ '0': 1, 'length': 1 }), false);
+      assert.strictEqual(_.isArray(1), false);
+      assert.strictEqual(_.isArray(/x/), false);
+      assert.strictEqual(_.isArray('a'), false);
+      assert.strictEqual(_.isArray(symbol), false);
+    });
+
+    QUnit.test('should work with an array from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.array) {
+        assert.strictEqual(_.isArray(realm.array), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isArrayBuffer');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for array buffers', function(assert) {
+      assert.expect(1);
+
+      if (ArrayBuffer) {
+        assert.strictEqual(_.isArrayBuffer(arrayBuffer), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non array buffers', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isArrayBuffer(value) : _.isArrayBuffer();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isArrayBuffer(args), false);
+      assert.strictEqual(_.isArrayBuffer([1, 2, 3]), false);
+      assert.strictEqual(_.isArrayBuffer(true), false);
+      assert.strictEqual(_.isArrayBuffer(new Date), false);
+      assert.strictEqual(_.isArrayBuffer(new Error), false);
+      assert.strictEqual(_.isArrayBuffer(_), false);
+      assert.strictEqual(_.isArrayBuffer(slice), false);
+      assert.strictEqual(_.isArrayBuffer({ 'a': 1 }), false);
+      assert.strictEqual(_.isArrayBuffer(1), false);
+      assert.strictEqual(_.isArrayBuffer(/x/), false);
+      assert.strictEqual(_.isArrayBuffer('a'), false);
+      assert.strictEqual(_.isArrayBuffer(symbol), false);
+    });
+
+    QUnit.test('should work with array buffers from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.arrayBuffer) {
+        assert.strictEqual(_.isArrayBuffer(realm.arrayBuffer), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isArrayLike');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for array-like values', function(assert) {
+      assert.expect(1);
+
+      var values = [args, [1, 2, 3], { '0': 1, 'length': 1 }, 'a'],
+          expected = lodashStable.map(values, alwaysTrue),
+          actual = lodashStable.map(values, _.isArrayLike);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-arrays', function(assert) {
+      assert.expect(11);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === '';
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isArrayLike(value) : _.isArrayLike();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isArrayLike(true), false);
+      assert.strictEqual(_.isArrayLike(new Date), false);
+      assert.strictEqual(_.isArrayLike(new Error), false);
+      assert.strictEqual(_.isArrayLike(_), false);
+      assert.strictEqual(_.isArrayLike(generator), false);
+      assert.strictEqual(_.isArrayLike(slice), false);
+      assert.strictEqual(_.isArrayLike({ 'a': 1 }), false);
+      assert.strictEqual(_.isArrayLike(1), false);
+      assert.strictEqual(_.isArrayLike(/x/), false);
+      assert.strictEqual(_.isArrayLike(symbol), false);
+    });
+
+    QUnit.test('should work with an array from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.object) {
+        var values = [realm.arguments, realm.array, realm.string],
+            expected = lodashStable.map(values, alwaysTrue),
+            actual = lodashStable.map(values, _.isArrayLike);
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isBoolean');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for booleans', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.isBoolean(true), true);
+      assert.strictEqual(_.isBoolean(false), true);
+      assert.strictEqual(_.isBoolean(Object(true)), true);
+      assert.strictEqual(_.isBoolean(Object(false)), true);
+    });
+
+    QUnit.test('should return `false` for non-booleans', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === false;
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isBoolean(value) : _.isBoolean();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isBoolean(args), false);
+      assert.strictEqual(_.isBoolean([1, 2, 3]), false);
+      assert.strictEqual(_.isBoolean(new Date), false);
+      assert.strictEqual(_.isBoolean(new Error), false);
+      assert.strictEqual(_.isBoolean(_), false);
+      assert.strictEqual(_.isBoolean(slice), false);
+      assert.strictEqual(_.isBoolean({ 'a': 1 }), false);
+      assert.strictEqual(_.isBoolean(1), false);
+      assert.strictEqual(_.isBoolean(/x/), false);
+      assert.strictEqual(_.isBoolean('a'), false);
+      assert.strictEqual(_.isBoolean(symbol), false);
+    });
+
+    QUnit.test('should work with a boolean from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.boolean) {
+        assert.strictEqual(_.isBoolean(realm.boolean), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isBuffer');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for buffers', function(assert) {
+      assert.expect(1);
+
+      if (Buffer) {
+        assert.strictEqual(_.isBuffer(new Buffer(2)), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non-buffers', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isBuffer(value) : _.isBuffer();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isBuffer(args), false);
+      assert.strictEqual(_.isBuffer([1, 2, 3]), false);
+      assert.strictEqual(_.isBuffer(true), false);
+      assert.strictEqual(_.isBuffer(new Date), false);
+      assert.strictEqual(_.isBuffer(new Error), false);
+      assert.strictEqual(_.isBuffer(_), false);
+      assert.strictEqual(_.isBuffer(slice), false);
+      assert.strictEqual(_.isBuffer({ 'a': 1 }), false);
+      assert.strictEqual(_.isBuffer(1), false);
+      assert.strictEqual(_.isBuffer(/x/), false);
+      assert.strictEqual(_.isBuffer('a'), false);
+      assert.strictEqual(_.isBuffer(symbol), false);
+    });
+
+    QUnit.test('should return `false` if `Buffer` is not defined', function(assert) {
+      assert.expect(1);
+
+      if (!isStrict && Buffer && lodashBizarro) {
+        assert.strictEqual(lodashBizarro.isBuffer(new Buffer(2)), false);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isDate');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for dates', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isDate(new Date), true);
+    });
+
+    QUnit.test('should return `false` for non-dates', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isDate(value) : _.isDate();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isDate(args), false);
+      assert.strictEqual(_.isDate([1, 2, 3]), false);
+      assert.strictEqual(_.isDate(true), false);
+      assert.strictEqual(_.isDate(new Error), false);
+      assert.strictEqual(_.isDate(_), false);
+      assert.strictEqual(_.isDate(slice), false);
+      assert.strictEqual(_.isDate({ 'a': 1 }), false);
+      assert.strictEqual(_.isDate(1), false);
+      assert.strictEqual(_.isDate(/x/), false);
+      assert.strictEqual(_.isDate('a'), false);
+      assert.strictEqual(_.isDate(symbol), false);
+    });
+
+    QUnit.test('should work with a date object from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.date) {
+        assert.strictEqual(_.isDate(realm.date), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isElement');
+
+  (function() {
+    var args = arguments;
+
+    function Element() {
+      this.nodeType = 1;
+    }
+
+    QUnit.test('should return `false` for plain objects', function(assert) {
+      assert.expect(7);
+
+      var element = body || new Element;
+
+      assert.strictEqual(_.isElement(element), true);
+      assert.strictEqual(_.isElement({ 'nodeType': 1 }), false);
+      assert.strictEqual(_.isElement({ 'nodeType': Object(1) }), false);
+      assert.strictEqual(_.isElement({ 'nodeType': true }), false);
+      assert.strictEqual(_.isElement({ 'nodeType': [1] }), false);
+      assert.strictEqual(_.isElement({ 'nodeType': '1' }), false);
+      assert.strictEqual(_.isElement({ 'nodeType': '001' }), false);
+    });
+
+    QUnit.test('should return `false` for non DOM elements', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isElement(value) : _.isElement();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isElement(args), false);
+      assert.strictEqual(_.isElement([1, 2, 3]), false);
+      assert.strictEqual(_.isElement(true), false);
+      assert.strictEqual(_.isElement(new Date), false);
+      assert.strictEqual(_.isElement(new Error), false);
+      assert.strictEqual(_.isElement(_), false);
+      assert.strictEqual(_.isElement(slice), false);
+      assert.strictEqual(_.isElement({ 'a': 1 }), false);
+      assert.strictEqual(_.isElement(1), false);
+      assert.strictEqual(_.isElement(/x/), false);
+      assert.strictEqual(_.isElement('a'), false);
+      assert.strictEqual(_.isElement(symbol), false);
+    });
+
+    QUnit.test('should work with a DOM element from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.element) {
+        assert.strictEqual(_.isElement(realm.element), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isEmpty');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for empty values', function(assert) {
+      assert.expect(10);
+
+      var expected = lodashStable.map(empties, alwaysTrue),
+          actual = lodashStable.map(empties, _.isEmpty);
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isEmpty(true), true);
+      assert.strictEqual(_.isEmpty(slice), true);
+      assert.strictEqual(_.isEmpty(1), true);
+      assert.strictEqual(_.isEmpty(NaN), true);
+      assert.strictEqual(_.isEmpty(/x/), true);
+      assert.strictEqual(_.isEmpty(symbol), true);
+      assert.strictEqual(_.isEmpty(), true);
+
+      if (Buffer) {
+        assert.strictEqual(_.isEmpty(new Buffer(0)), true);
+        assert.strictEqual(_.isEmpty(new Buffer(1)), false);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should return `false` for non-empty values', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.isEmpty([0]), false);
+      assert.strictEqual(_.isEmpty({ 'a': 0 }), false);
+      assert.strictEqual(_.isEmpty('a'), false);
+    });
+
+    QUnit.test('should work with an object that has a `length` property', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isEmpty({ 'length': 0 }), false);
+    });
+
+    QUnit.test('should work with `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isEmpty(args), false);
+    });
+
+    QUnit.test('should work with jQuery/MooTools DOM query collections', function(assert) {
+      assert.expect(1);
+
+      function Foo(elements) {
+        push.apply(this, elements);
+      }
+      Foo.prototype = { 'length': 0, 'splice': arrayProto.splice };
+
+      assert.strictEqual(_.isEmpty(new Foo([])), true);
+    });
+
+    QUnit.test('should work with maps', function(assert) {
+      assert.expect(4);
+
+      if (Map) {
+        lodashStable.each([new Map, realm.map], function(map) {
+          assert.strictEqual(_.isEmpty(map), true);
+          map.set('a', 1);
+          assert.strictEqual(_.isEmpty(map), false);
+          map.clear();
+        });
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should work with sets', function(assert) {
+      assert.expect(4);
+
+      if (Set) {
+        lodashStable.each([new Set, realm.set], function(set) {
+          assert.strictEqual(_.isEmpty(set), true);
+          set.add(1);
+          assert.strictEqual(_.isEmpty(set), false);
+          set.clear();
+        });
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should not treat objects with negative lengths as array-like', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype.length = -1;
+
+      assert.strictEqual(_.isEmpty(new Foo), true);
+    });
+
+    QUnit.test('should not treat objects with lengths larger than `MAX_SAFE_INTEGER` as array-like', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype.length = MAX_SAFE_INTEGER + 1;
+
+      assert.strictEqual(_.isEmpty(new Foo), true);
+    });
+
+    QUnit.test('should not treat objects with non-number lengths as array-like', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isEmpty({ 'length': '0' }), false);
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_({}).isEmpty(), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_({}).chain().isEmpty() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isEqual');
+
+  (function() {
+    var symbol1 = Symbol ? Symbol('a') : true,
+        symbol2 = Symbol ? Symbol('b') : false;
+
+    QUnit.test('should compare primitives', function(assert) {
+      assert.expect(1);
+
+      var pairs = [
+        [1, 1, true], [1, Object(1), true], [1, '1', false], [1, 2, false],
+        [-0, -0, true], [0, 0, true], [0, Object(0), true], [Object(0), Object(0), true], [-0, 0, true], [0, '0', false], [0, null, false],
+        [NaN, NaN, true], [NaN, Object(NaN), true], [Object(NaN), Object(NaN), true], [NaN, 'a', false], [NaN, Infinity, false],
+        ['a', 'a', true], ['a', Object('a'), true], [Object('a'), Object('a'), true], ['a', 'b', false], ['a', ['a'], false],
+        [true, true, true], [true, Object(true), true], [Object(true), Object(true), true], [true, 1, false], [true, 'a', false],
+        [false, false, true], [false, Object(false), true], [Object(false), Object(false), true], [false, 0, false], [false, '', false],
+        [symbol1, symbol1, true], [symbol1, Object(symbol1), true], [Object(symbol1), Object(symbol1), true], [symbol1, symbol2, false],
+        [null, null, true], [null, undefined, false], [null, {}, false], [null, '', false],
+        [undefined, undefined, true], [undefined, null, false], [undefined, '', false]
+      ];
+
+      var expected = lodashStable.map(pairs, function(pair) {
+        return pair[2];
+      });
+
+      var actual = lodashStable.map(pairs, function(pair) {
+        return _.isEqual(pair[0], pair[1]);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should compare arrays', function(assert) {
+      assert.expect(6);
+
+      var array1 = [true, null, 1, 'a', undefined],
+          array2 = [true, null, 1, 'a', undefined];
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { 'e': 1 }];
+      array2 = [[1, 2, 3], new Date(2012, 4, 23), /x/, { 'e': 1 }];
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1 = [1];
+      array1[2] = 3;
+
+      array2 = [1];
+      array2[1] = undefined;
+      array2[2] = 3;
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1 = [Object(1), false, Object('a'), /x/, new Date(2012, 4, 23), ['a', 'b', [Object('c')]], { 'a': 1 }];
+      array2 = [1, Object(false), 'a', /x/, new Date(2012, 4, 23), ['a', Object('b'), ['c']], { 'a': 1 }];
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1 = [1, 2, 3];
+      array2 = [3, 2, 1];
+
+      assert.strictEqual(_.isEqual(array1, array2), false);
+
+      array1 = [1, 2];
+      array2 = [1, 2, 3];
+
+      assert.strictEqual(_.isEqual(array1, array2), false);
+    });
+
+    QUnit.test('should treat arrays with identical values but different non-index properties as equal', function(assert) {
+      assert.expect(3);
+
+      var array1 = [1, 2, 3],
+          array2 = [1, 2, 3];
+
+      array1.every = array1.filter = array1.forEach =
+      array1.indexOf = array1.lastIndexOf = array1.map =
+      array1.some = array1.reduce = array1.reduceRight = null;
+
+      array2.concat = array2.join = array2.pop =
+      array2.reverse = array2.shift = array2.slice =
+      array2.sort = array2.splice = array2.unshift = null;
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1 = [1, 2, 3];
+      array1.a = 1;
+
+      array2 = [1, 2, 3];
+      array2.b = 1;
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1 = /c/.exec('abcde');
+      array2 = ['c'];
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+    });
+
+    QUnit.test('should compare sparse arrays', function(assert) {
+      assert.expect(3);
+
+      var array = Array(1);
+
+      assert.strictEqual(_.isEqual(array, Array(1)), true);
+      assert.strictEqual(_.isEqual(array, [undefined]), true);
+      assert.strictEqual(_.isEqual(array, Array(2)), false);
+    });
+
+    QUnit.test('should compare plain objects', function(assert) {
+      assert.expect(5);
+
+      var object1 = { 'a': true, 'b': null, 'c': 1, 'd': 'a', 'e': undefined },
+          object2 = { 'a': true, 'b': null, 'c': 1, 'd': 'a', 'e': undefined };
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+
+      object1 = { 'a': [1, 2, 3], 'b': new Date(2012, 4, 23), 'c': /x/, 'd': { 'e': 1 } };
+      object2 = { 'a': [1, 2, 3], 'b': new Date(2012, 4, 23), 'c': /x/, 'd': { 'e': 1 } };
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+
+      object1 = { 'a': 1, 'b': 2, 'c': 3 };
+      object2 = { 'a': 3, 'b': 2, 'c': 1 };
+
+      assert.strictEqual(_.isEqual(object1, object2), false);
+
+      object1 = { 'a': 1, 'b': 2, 'c': 3 };
+      object2 = { 'd': 1, 'e': 2, 'f': 3 };
+
+      assert.strictEqual(_.isEqual(object1, object2), false);
+
+      object1 = { 'a': 1, 'b': 2 };
+      object2 = { 'a': 1, 'b': 2, 'c': 3 };
+
+      assert.strictEqual(_.isEqual(object1, object2), false);
+    });
+
+    QUnit.test('should compare objects regardless of key order', function(assert) {
+      assert.expect(1);
+
+      var object1 = { 'a': 1, 'b': 2, 'c': 3 },
+          object2 = { 'c': 3, 'a': 1, 'b': 2 };
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+    });
+
+    QUnit.test('should compare nested objects', function(assert) {
+      assert.expect(1);
+
+      var object1 = {
+        'a': [1, 2, 3],
+        'b': true,
+        'c': Object(1),
+        'd': 'a',
+        'e': {
+          'f': ['a', Object('b'), 'c'],
+          'g': Object(false),
+          'h': new Date(2012, 4, 23),
+          'i': noop,
+          'j': 'a'
+        }
+      };
+
+      var object2 = {
+        'a': [1, Object(2), 3],
+        'b': Object(true),
+        'c': 1,
+        'd': Object('a'),
+        'e': {
+          'f': ['a', 'b', 'c'],
+          'g': false,
+          'h': new Date(2012, 4, 23),
+          'i': noop,
+          'j': 'a'
+        }
+      };
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+    });
+
+    QUnit.test('should compare object instances', function(assert) {
+      assert.expect(4);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.a = 1;
+
+      function Bar() {
+        this.a = 1;
+      }
+      Bar.prototype.a = 2;
+
+      assert.strictEqual(_.isEqual(new Foo, new Foo), true);
+      assert.strictEqual(_.isEqual(new Foo, new Bar), false);
+      assert.strictEqual(_.isEqual({ 'a': 1 }, new Foo), false);
+      assert.strictEqual(_.isEqual({ 'a': 2 }, new Bar), false);
+    });
+
+    QUnit.test('should compare objects with constructor properties', function(assert) {
+      assert.expect(5);
+
+      assert.strictEqual(_.isEqual({ 'constructor': 1 },   { 'constructor': 1 }), true);
+      assert.strictEqual(_.isEqual({ 'constructor': 1 },   { 'constructor': '1' }), false);
+      assert.strictEqual(_.isEqual({ 'constructor': [1] }, { 'constructor': [1] }), true);
+      assert.strictEqual(_.isEqual({ 'constructor': [1] }, { 'constructor': ['1'] }), false);
+      assert.strictEqual(_.isEqual({ 'constructor': Object }, {}), false);
+    });
+
+    QUnit.test('should compare arrays with circular references', function(assert) {
+      assert.expect(4);
+
+      var array1 = [],
+          array2 = [];
+
+      array1.push(array1);
+      array2.push(array2);
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1.push('b');
+      array2.push('b');
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1.push('c');
+      array2.push('d');
+
+      assert.strictEqual(_.isEqual(array1, array2), false);
+
+      array1 = ['a', 'b', 'c'];
+      array1[1] = array1;
+      array2 = ['a', ['a', 'b', 'c'], 'c'];
+
+      assert.strictEqual(_.isEqual(array1, array2), false);
+    });
+
+    QUnit.test('should compare objects with circular references', function(assert) {
+      assert.expect(4);
+
+      var object1 = {},
+          object2 = {};
+
+      object1.a = object1;
+      object2.a = object2;
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+
+      object1.b = 0;
+      object2.b = Object(0);
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+
+      object1.c = Object(1);
+      object2.c = Object(2);
+
+      assert.strictEqual(_.isEqual(object1, object2), false);
+
+      object1 = { 'a': 1, 'b': 2, 'c': 3 };
+      object1.b = object1;
+      object2 = { 'a': 1, 'b': { 'a': 1, 'b': 2, 'c': 3 }, 'c': 3 };
+
+      assert.strictEqual(_.isEqual(object1, object2), false);
+    });
+
+    QUnit.test('should compare objects with multiple circular references', function(assert) {
+      assert.expect(3);
+
+      var array1 = [{}],
+          array2 = [{}];
+
+      (array1[0].a = array1).push(array1);
+      (array2[0].a = array2).push(array2);
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1[0].b = 0;
+      array2[0].b = Object(0);
+
+      assert.strictEqual(_.isEqual(array1, array2), true);
+
+      array1[0].c = Object(1);
+      array2[0].c = Object(2);
+
+      assert.strictEqual(_.isEqual(array1, array2), false);
+    });
+
+    QUnit.test('should compare objects with complex circular references', function(assert) {
+      assert.expect(1);
+
+      var object1 = {
+        'foo': { 'b': { 'c': { 'd': {} } } },
+        'bar': { 'a': 2 }
+      };
+
+      var object2 = {
+        'foo': { 'b': { 'c': { 'd': {} } } },
+        'bar': { 'a': 2 }
+      };
+
+      object1.foo.b.c.d = object1;
+      object1.bar.b = object1.foo.b;
+
+      object2.foo.b.c.d = object2;
+      object2.bar.b = object2.foo.b;
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+    });
+
+    QUnit.test('should compare objects with shared property values', function(assert) {
+      assert.expect(1);
+
+      var object1 = {
+        'a': [1, 2]
+      };
+
+      var object2 = {
+        'a': [1, 2],
+        'b': [1, 2]
+      };
+
+      object1.b = object1.a;
+
+      assert.strictEqual(_.isEqual(object1, object2), true);
+    });
+
+    QUnit.test('should treat objects created by `Object.create(null)` like a plain object', function(assert) {
+      assert.expect(2);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.constructor = null;
+
+      var object2 = { 'a': 1 };
+      assert.strictEqual(_.isEqual(new Foo, object2), false);
+
+      if (create)  {
+        var object1 = create(null);
+        object1.a = 1;
+        assert.strictEqual(_.isEqual(object1, object2), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for objects with custom `toString` methods', function(assert) {
+      assert.expect(1);
+
+      var primitive,
+          object = { 'toString': function() { return primitive; } },
+          values = [true, null, 1, 'a', undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value) {
+        primitive = value;
+        return _.isEqual(object, value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should avoid common type coercions', function(assert) {
+      assert.expect(9);
+
+      assert.strictEqual(_.isEqual(true, Object(false)), false);
+      assert.strictEqual(_.isEqual(Object(false), Object(0)), false);
+      assert.strictEqual(_.isEqual(false, Object('')), false);
+      assert.strictEqual(_.isEqual(Object(36), Object('36')), false);
+      assert.strictEqual(_.isEqual(0, ''), false);
+      assert.strictEqual(_.isEqual(1, true), false);
+      assert.strictEqual(_.isEqual(1337756400000, new Date(2012, 4, 23)), false);
+      assert.strictEqual(_.isEqual('36', 36), false);
+      assert.strictEqual(_.isEqual(36, '36'), false);
+    });
+
+    QUnit.test('should compare `arguments` objects', function(assert) {
+      assert.expect(2);
+
+      var args1 = (function() { return arguments; }(1, 2, 3)),
+          args2 = (function() { return arguments; }(1, 2, 3)),
+          args3 = (function() { return arguments; }(1, 2));
+
+      assert.strictEqual(_.isEqual(args1, args2), true);
+      assert.strictEqual(_.isEqual(args1, args3), false);
+    });
+
+    QUnit.test('should treat `arguments` objects like `Object` objects', function(assert) {
+      assert.expect(4);
+
+      var args = (function() { return arguments; }(1, 2, 3)),
+          object = { '0': 1, '1': 2, '2': 3 };
+
+      function Foo() {}
+      Foo.prototype = object;
+
+      assert.strictEqual(_.isEqual(args, object), true);
+      assert.strictEqual(_.isEqual(object, args), true);
+
+      assert.strictEqual(_.isEqual(args, new Foo), false);
+      assert.strictEqual(_.isEqual(new Foo, args), false);
+    });
+
+    QUnit.test('should compare array buffers', function(assert) {
+      assert.expect(2);
+
+      if (ArrayBuffer) {
+        var buffer1 = new ArrayBuffer(4),
+            buffer2 = new ArrayBuffer(8);
+
+        assert.strictEqual(_.isEqual(buffer1, buffer2), false);
+
+        buffer1 = new Int8Array([-1]).buffer;
+        buffer2 = new Uint8Array([255]).buffer;
+
+        assert.strictEqual(_.isEqual(buffer1, buffer2), true);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should compare array views', function(assert) {
+      assert.expect(2);
+
+      lodashStable.times(2, function(index) {
+        var ns = index ? realm : root;
+
+        var pairs = lodashStable.map(arrayViews, function(type, viewIndex) {
+          var otherType = arrayViews[(viewIndex + 1) % arrayViews.length],
+              CtorA = ns[type] || function(n) { this.n = n; },
+              CtorB = ns[otherType] || function(n) { this.n = n; },
+              bufferA = ns[type] ? new ns.ArrayBuffer(8) : 8,
+              bufferB = ns[otherType] ? new ns.ArrayBuffer(8) : 8,
+              bufferC = ns[otherType] ? new ns.ArrayBuffer(16) : 16;
+
+          return [new CtorA(bufferA), new CtorA(bufferA), new CtorB(bufferB), new CtorB(bufferC)];
+        });
+
+        var expected = lodashStable.map(pairs, lodashStable.constant([true, false, false]));
+
+        var actual = lodashStable.map(pairs, function(pair) {
+          return [_.isEqual(pair[0], pair[1]), _.isEqual(pair[0], pair[2]), _.isEqual(pair[2], pair[3])];
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should compare date objects', function(assert) {
+      assert.expect(4);
+
+      var date = new Date(2012, 4, 23);
+
+      assert.strictEqual(_.isEqual(date, new Date(2012, 4, 23)), true);
+      assert.strictEqual(_.isEqual(date, new Date(2013, 3, 25)), false);
+      assert.strictEqual(_.isEqual(date, { 'getTime': lodashStable.constant(+date) }), false);
+      assert.strictEqual(_.isEqual(new Date('a'), new Date('a')), false);
+    });
+
+    QUnit.test('should compare error objects', function(assert) {
+      assert.expect(1);
+
+      var pairs = lodashStable.map([
+        'Error',
+        'EvalError',
+        'RangeError',
+        'ReferenceError',
+        'SyntaxError',
+        'TypeError',
+        'URIError'
+      ], function(type, index, errorTypes) {
+        var otherType = errorTypes[++index % errorTypes.length],
+            CtorA = root[type],
+            CtorB = root[otherType];
+
+        return [new CtorA('a'), new CtorA('a'), new CtorB('a'), new CtorB('b')];
+      });
+
+      var expected = lodashStable.map(pairs, lodashStable.constant([true, false, false]));
+
+      var actual = lodashStable.map(pairs, function(pair) {
+        return [_.isEqual(pair[0], pair[1]), _.isEqual(pair[0], pair[2]), _.isEqual(pair[2], pair[3])];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should compare functions', function(assert) {
+      assert.expect(2);
+
+      function a() { return 1 + 2; }
+      function b() { return 1 + 2; }
+
+      assert.strictEqual(_.isEqual(a, a), true);
+      assert.strictEqual(_.isEqual(a, b), false);
+    });
+
+    QUnit.test('should compare maps', function(assert) {
+      assert.expect(8);
+
+      if (Map) {
+        lodashStable.each([[map, new Map], [map, realm.map]], function(maps) {
+          var map1 = maps[0],
+              map2 = maps[1];
+
+          map1.set('a', 1);
+          map2.set('b', 2);
+          assert.strictEqual(_.isEqual(map1, map2), false);
+
+          map1.set('b', 2);
+          map2.set('a', 1);
+          assert.strictEqual(_.isEqual(map1, map2), true);
+
+          map1['delete']('a');
+          map1.set('a', 1);
+          assert.strictEqual(_.isEqual(map1, map2), true);
+
+          map2['delete']('a');
+          assert.strictEqual(_.isEqual(map1, map2), false);
+
+          map1.clear();
+          map2.clear();
+        });
+      }
+      else {
+        skipAssert(assert, 8);
+      }
+    });
+
+    QUnit.test('should compare maps with circular references', function(assert) {
+      assert.expect(2);
+
+      if (Map) {
+        var map1 = new Map,
+            map2 = new Map;
+
+        map1.set('a', map1);
+        map2.set('a', map2);
+        assert.strictEqual(_.isEqual(map1, map2), true);
+
+        map1.set('b', 1);
+        map2.set('b', 2);
+        assert.strictEqual(_.isEqual(map1, map2), false);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should compare promises by reference', function(assert) {
+      assert.expect(4);
+
+      if (promise) {
+        lodashStable.each([[promise, Promise.resolve(1)], [promise, realm.promise]], function(promises) {
+          var promise1 = promises[0],
+              promise2 = promises[1];
+
+          assert.strictEqual(_.isEqual(promise1, promise2), false);
+          assert.strictEqual(_.isEqual(promise1, promise1), true);
+        });
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should compare regexes', function(assert) {
+      assert.expect(5);
+
+      assert.strictEqual(_.isEqual(/x/gim, /x/gim), true);
+      assert.strictEqual(_.isEqual(/x/gim, /x/mgi), true);
+      assert.strictEqual(_.isEqual(/x/gi, /x/g), false);
+      assert.strictEqual(_.isEqual(/x/, /y/), false);
+      assert.strictEqual(_.isEqual(/x/g, { 'global': true, 'ignoreCase': false, 'multiline': false, 'source': 'x' }), false);
+    });
+
+    QUnit.test('should compare sets', function(assert) {
+      assert.expect(8);
+
+      if (Set) {
+        lodashStable.each([[set, new Set], [set, realm.set]], function(sets) {
+          var set1 = sets[0],
+              set2 = sets[1];
+
+          set1.add(1);
+          set2.add(2);
+          assert.strictEqual(_.isEqual(set1, set2), false);
+
+          set1.add(2);
+          set2.add(1);
+          assert.strictEqual(_.isEqual(set1, set2), true);
+
+          set1['delete'](1);
+          set1.add(1);
+          assert.strictEqual(_.isEqual(set1, set2), true);
+
+          set2['delete'](1);
+          assert.strictEqual(_.isEqual(set1, set2), false);
+
+          set1.clear();
+          set2.clear();
+        });
+      }
+      else {
+        skipAssert(assert, 8);
+      }
+    });
+
+    QUnit.test('should compare sets with circular references', function(assert) {
+      assert.expect(2);
+
+      if (Set) {
+        var set1 = new Set,
+            set2 = new Set;
+
+        set1.add(set1);
+        set2.add(set2);
+        assert.strictEqual(_.isEqual(set1, set2), true);
+
+        set1.add(1);
+        set2.add(2);
+        assert.strictEqual(_.isEqual(set1, set2), false);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should work as an iteratee for `_.every`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.every([1, 1, 1], lodashStable.partial(_.isEqual, 1));
+      assert.ok(actual);
+    });
+
+    QUnit.test('should return `true` for like-objects from different documents', function(assert) {
+      assert.expect(4);
+
+      if (realm.object) {
+        assert.strictEqual(_.isEqual([1], realm.array), true);
+        assert.strictEqual(_.isEqual([2], realm.array), false);
+        assert.strictEqual(_.isEqual({ 'a': 1 }, realm.object), true);
+        assert.strictEqual(_.isEqual({ 'a': 2 }, realm.object), false);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should not error on DOM elements', function(assert) {
+      assert.expect(1);
+
+      if (document) {
+        var element1 = document.createElement('div'),
+            element2 = element1.cloneNode(true);
+
+        try {
+          assert.strictEqual(_.isEqual(element1, element2), false);
+        } catch (e) {
+          assert.ok(false, e.message);
+        }
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should compare wrapped values', function(assert) {
+      assert.expect(32);
+
+      var stamp = +new Date;
+
+      var values = [
+        [[1, 2], [1, 2], [1, 2, 3]],
+        [true, true, false],
+        [new Date(stamp), new Date(stamp), new Date(stamp - 100)],
+        [{ 'a': 1, 'b': 2 }, { 'a': 1, 'b': 2 }, { 'a': 1, 'b': 1 }],
+        [1, 1, 2],
+        [NaN, NaN, Infinity],
+        [/x/, /x/, /x/i],
+        ['a', 'a', 'A']
+      ];
+
+      lodashStable.each(values, function(vals) {
+        if (!isNpm) {
+          var wrapped1 = _(vals[0]),
+              wrapped2 = _(vals[1]),
+              actual = wrapped1.isEqual(wrapped2);
+
+          assert.strictEqual(actual, true);
+          assert.strictEqual(_.isEqual(_(actual), _(true)), true);
+
+          wrapped1 = _(vals[0]);
+          wrapped2 = _(vals[2]);
+
+          actual = wrapped1.isEqual(wrapped2);
+          assert.strictEqual(actual, false);
+          assert.strictEqual(_.isEqual(_(actual), _(false)), true);
+        }
+        else {
+          skipAssert(assert, 4);
+        }
+      });
+    });
+
+    QUnit.test('should compare wrapped and non-wrapped values', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var object1 = _({ 'a': 1, 'b': 2 }),
+            object2 = { 'a': 1, 'b': 2 };
+
+        assert.strictEqual(object1.isEqual(object2), true);
+        assert.strictEqual(_.isEqual(object1, object2), true);
+
+        object1 = _({ 'a': 1, 'b': 2 });
+        object2 = { 'a': 1, 'b': 1 };
+
+        assert.strictEqual(object1.isEqual(object2), false);
+        assert.strictEqual(_.isEqual(object1, object2), false);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_('a').isEqual('a'), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_('a').chain().isEqual('a') instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isEqualWith');
+
+  (function() {
+    QUnit.test('should provide the correct `customizer` arguments', function(assert) {
+      assert.expect(1);
+
+      var argsList = [],
+          object1 = { 'a': [1, 2], 'b': null },
+          object2 = { 'a': [1, 2], 'b': null };
+
+      object1.b = object2;
+      object2.b = object1;
+
+      var expected = [
+        [object1, object2],
+        [object1.a, object2.a, 'a', object1, object2],
+        [object1.a[0], object2.a[0], 0, object1.a, object2.a],
+        [object1.a[1], object2.a[1], 1, object1.a, object2.a],
+        [object1.b, object2.b, 'b', object1.b, object2.b],
+        [object1.b.a, object2.b.a, 'a', object1.b, object2.b],
+        [object1.b.a[0], object2.b.a[0], 0, object1.b.a, object2.b.a],
+        [object1.b.a[1], object2.b.a[1], 1, object1.b.a, object2.b.a],
+        [object1.b.b, object2.b.b, 'b', object1.b.b, object2.b.b]
+      ];
+
+      _.isEqualWith(object1, object2, function(assert) {
+        var length = arguments.length,
+            args = slice.call(arguments, 0, length - (length > 2 ? 1 : 0));
+
+        argsList.push(args);
+      });
+
+      assert.deepEqual(argsList, expected);
+    });
+
+    QUnit.test('should handle comparisons if `customizer` returns `undefined`', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.isEqualWith('a', 'a', noop), true);
+      assert.strictEqual(_.isEqualWith(['a'], ['a'], noop), true);
+      assert.strictEqual(_.isEqualWith({ '0': 'a' }, { '0': 'a' }, noop), true);
+    });
+
+    QUnit.test('should not handle comparisons if `customizer` returns `true`', function(assert) {
+      assert.expect(3);
+
+      var customizer = function(value) {
+        return _.isString(value) || undefined;
+      };
+
+      assert.strictEqual(_.isEqualWith('a', 'b', customizer), true);
+      assert.strictEqual(_.isEqualWith(['a'], ['b'], customizer), true);
+      assert.strictEqual(_.isEqualWith({ '0': 'a' }, { '0': 'b' }, customizer), true);
+    });
+
+    QUnit.test('should not handle comparisons if `customizer` returns `false`', function(assert) {
+      assert.expect(3);
+
+      var customizer = function(value) {
+        return _.isString(value) ? false : undefined;
+      };
+
+      assert.strictEqual(_.isEqualWith('a', 'a', customizer), false);
+      assert.strictEqual(_.isEqualWith(['a'], ['a'], customizer), false);
+      assert.strictEqual(_.isEqualWith({ '0': 'a' }, { '0': 'a' }, customizer), false);
+    });
+
+    QUnit.test('should return a boolean value even if `customizer` does not', function(assert) {
+      assert.expect(2);
+
+      var actual = _.isEqualWith('a', 'b', alwaysC);
+      assert.strictEqual(actual, true);
+
+      var values = _.without(falsey, undefined),
+          expected = lodashStable.map(values, alwaysFalse);
+
+      actual = [];
+      lodashStable.each(values, function(value) {
+        actual.push(_.isEqualWith('a', 'a', lodashStable.constant(value)));
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should ensure `customizer` is a function', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3],
+          eq = _.partial(_.isEqualWith, array),
+          actual = lodashStable.map([array, [1, 0, 3]], eq);
+
+      assert.deepEqual(actual, [true, false]);
+    });
+
+    QUnit.test('should call `customizer` for values maps and sets', function(assert) {
+      assert.expect(2);
+
+      var value = { 'a': { 'b': 2 } };
+
+      if (Map) {
+        var map1 = new Map;
+        map1.set('a', value);
+
+        var map2 = new Map;
+        map2.set('a', value);
+      }
+      if (Set) {
+        var set1 = new Set;
+        set1.add(value);
+
+        var set2 = new Set;
+        set2.add(value);
+      }
+      lodashStable.each([[map1, map2], [set1, set2]], function(pair, index) {
+        if (pair[0]) {
+          var argsList = [],
+              array = _.toArray(pair[0]);
+
+          var expected = [
+            [pair[0], pair[1]],
+            [array[0], array[0], 0, array, array],
+            [array[0][0], array[0][0], 0, array[0], array[0]],
+            [array[0][1], array[0][1], 1, array[0], array[0]]
+          ];
+
+          if (index) {
+            expected.length = 2;
+          }
+          _.isEqualWith(pair[0], pair[1], function() {
+            var length = arguments.length,
+                args = slice.call(arguments, 0, length - (length > 2 ? 1 : 0));
+
+            argsList.push(args);
+          });
+
+          assert.deepEqual(argsList, expected, index ? 'Set' : 'Map');
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isError');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for error objects', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(errors, alwaysTrue);
+
+      var actual = lodashStable.map(errors, function(error) {
+        return _.isError(error) === true;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` for subclassed values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isError(new CustomError('x')), true);
+    });
+
+    QUnit.test('should return `false` for non error objects', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isError(value) : _.isError();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isError(args), false);
+      assert.strictEqual(_.isError([1, 2, 3]), false);
+      assert.strictEqual(_.isError(true), false);
+      assert.strictEqual(_.isError(new Date), false);
+      assert.strictEqual(_.isError(_), false);
+      assert.strictEqual(_.isError(slice), false);
+      assert.strictEqual(_.isError({ 'a': 1 }), false);
+      assert.strictEqual(_.isError(1), false);
+      assert.strictEqual(_.isError(/x/), false);
+      assert.strictEqual(_.isError('a'), false);
+      assert.strictEqual(_.isError(symbol), false);
+    });
+
+    QUnit.test('should work with an error object from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.errors) {
+        var expected = lodashStable.map(realm.errors, alwaysTrue);
+
+        var actual = lodashStable.map(realm.errors, function(error) {
+          return _.isError(error) === true;
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isFinite');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for finite values', function(assert) {
+      assert.expect(1);
+
+      var values = [0, 1, 3.14, -1],
+          expected = lodashStable.map(values, alwaysTrue),
+          actual = lodashStable.map(values, _.isFinite);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-finite values', function(assert) {
+      assert.expect(1);
+
+      var values = [NaN, Infinity, -Infinity, Object(1)],
+          expected = lodashStable.map(values, alwaysFalse),
+          actual = lodashStable.map(values, _.isFinite);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-numeric values', function(assert) {
+      assert.expect(10);
+
+      var values = [undefined, [], true, '', ' ', '2px'],
+          expected = lodashStable.map(values, alwaysFalse),
+          actual = lodashStable.map(values, _.isFinite);
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isFinite(args), false);
+      assert.strictEqual(_.isFinite([1, 2, 3]), false);
+      assert.strictEqual(_.isFinite(true), false);
+      assert.strictEqual(_.isFinite(new Date), false);
+      assert.strictEqual(_.isFinite(new Error), false);
+      assert.strictEqual(_.isFinite({ 'a': 1 }), false);
+      assert.strictEqual(_.isFinite(/x/), false);
+      assert.strictEqual(_.isFinite('a'), false);
+      assert.strictEqual(_.isFinite(symbol), false);
+    });
+
+    QUnit.test('should return `false` for numeric string values', function(assert) {
+      assert.expect(1);
+
+      var values = ['2', '0', '08'],
+          expected = lodashStable.map(values, alwaysFalse),
+          actual = lodashStable.map(values, _.isFinite);
+
+      assert.deepEqual(actual, expected);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isFunction');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for functions', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isFunction(_), true);
+      assert.strictEqual(_.isFunction(slice), true);
+    });
+
+    QUnit.test('should return `true` for generator functions', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isFunction(generator), typeof generator == 'function');
+    });
+
+    QUnit.test('should return `true` for array view constructors', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(arrayViews, function(type) {
+        return objToString.call(root[type]) == funcTag;
+      });
+
+      var actual = lodashStable.map(arrayViews, function(type) {
+        return _.isFunction(root[type]);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-functions', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isFunction(value) : _.isFunction();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isFunction(args), false);
+      assert.strictEqual(_.isFunction([1, 2, 3]), false);
+      assert.strictEqual(_.isFunction(true), false);
+      assert.strictEqual(_.isFunction(new Date), false);
+      assert.strictEqual(_.isFunction(new Error), false);
+      assert.strictEqual(_.isFunction({ 'a': 1 }), false);
+      assert.strictEqual(_.isFunction(1), false);
+      assert.strictEqual(_.isFunction(/x/), false);
+      assert.strictEqual(_.isFunction('a'), false);
+      assert.strictEqual(_.isFunction(symbol), false);
+
+      if (document) {
+        assert.strictEqual(_.isFunction(document.getElementsByTagName('body')), false);
+      } else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work with host objects in IE 8 document mode (test in IE 11)', function(assert) {
+      assert.expect(2);
+
+      // Trigger a Chakra JIT bug.
+      // See https://github.com/jashkenas/underscore/issues/1621.
+      lodashStable.each([body, xml], function(object) {
+        if (object) {
+          lodashStable.times(100, _.isFunction);
+          assert.strictEqual(_.isFunction(object), false);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    QUnit.test('should work with a function from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.function) {
+        assert.strictEqual(_.isFunction(realm.function), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('isInteger methods');
+
+  lodashStable.each(['isInteger', 'isSafeInteger'], function(methodName) {
+    var args = arguments,
+        func = _[methodName],
+        isSafe = methodName == 'isSafeInteger';
+
+    QUnit.test('`_.' + methodName + '` should return `true` for integer values', function(assert) {
+      assert.expect(2);
+
+      var values = [-1, 0, 1],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(value);
+      });
+
+      assert.deepEqual(actual, expected);
+      assert.strictEqual(func(MAX_INTEGER), !isSafe);
+    });
+
+    QUnit.test('should return `false` for non-integer number values', function(assert) {
+      assert.expect(1);
+
+      var values = [NaN, Infinity, -Infinity, Object(1), 3.14],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-numeric values', function(assert) {
+      assert.expect(10);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === 0;
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? func(value) : func();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(func(args), false);
+      assert.strictEqual(func([1, 2, 3]), false);
+      assert.strictEqual(func(true), false);
+      assert.strictEqual(func(new Date), false);
+      assert.strictEqual(func(new Error), false);
+      assert.strictEqual(func({ 'a': 1 }), false);
+      assert.strictEqual(func(/x/), false);
+      assert.strictEqual(func('a'), false);
+      assert.strictEqual(func(symbol), false);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isLength');
+
+  (function() {
+    QUnit.test('should return `true` for lengths', function(assert) {
+      assert.expect(1);
+
+      var values = [0, 3, MAX_SAFE_INTEGER],
+          expected = lodashStable.map(values, alwaysTrue),
+          actual = lodashStable.map(values, _.isLength);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-lengths', function(assert) {
+      assert.expect(1);
+
+      var values = [-1, '1', 1.1, MAX_SAFE_INTEGER + 1],
+          expected = lodashStable.map(values, alwaysFalse),
+          actual = lodashStable.map(values, _.isLength);
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isMap');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for maps', function(assert) {
+      assert.expect(1);
+
+      if (Map) {
+        assert.strictEqual(_.isMap(map), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non-maps', function(assert) {
+      assert.expect(14);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isMap(value) : _.isMap();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isMap(args), false);
+      assert.strictEqual(_.isMap([1, 2, 3]), false);
+      assert.strictEqual(_.isMap(true), false);
+      assert.strictEqual(_.isMap(new Date), false);
+      assert.strictEqual(_.isMap(new Error), false);
+      assert.strictEqual(_.isMap(_), false);
+      assert.strictEqual(_.isMap(slice), false);
+      assert.strictEqual(_.isMap({ 'a': 1 }), false);
+      assert.strictEqual(_.isMap(1), false);
+      assert.strictEqual(_.isMap(/x/), false);
+      assert.strictEqual(_.isMap('a'), false);
+      assert.strictEqual(_.isMap(symbol), false);
+      assert.strictEqual(_.isMap(weakMap), false);
+    });
+
+    QUnit.test('should work for objects with a non-function `constructor` (test in IE 11)', function(assert) {
+      assert.expect(1);
+
+      var values = [false, true],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.isMap({ 'constructor': value });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with maps from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.map) {
+        assert.strictEqual(_.isMap(realm.map), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isMatch');
+
+  (function() {
+    QUnit.test('should perform a deep comparison between `object` and `source`', function(assert) {
+      assert.expect(5);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 };
+      assert.strictEqual(_.isMatch(object, { 'a': 1 }), true);
+      assert.strictEqual(_.isMatch(object, { 'b': 1 }), false);
+      assert.strictEqual(_.isMatch(object, { 'a': 1, 'c': 3 }), true);
+      assert.strictEqual(_.isMatch(object, { 'c': 3, 'd': 4 }), false);
+
+      object = { 'a': { 'b': { 'c': 1, 'd': 2 }, 'e': 3 }, 'f': 4 };
+      assert.strictEqual(_.isMatch(object, { 'a': { 'b': { 'c': 1 } } }), true);
+    });
+
+    QUnit.test('should match inherited string keyed `object` properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      assert.strictEqual(_.isMatch({ 'a': new Foo }, { 'a': { 'b': 2 } }), true);
+    });
+
+    QUnit.test('should not match by inherited `source` properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': 2 }],
+          source = new Foo,
+          expected = lodashStable.map(objects, alwaysTrue);
+
+      var actual = lodashStable.map(objects, function(object) {
+        return _.isMatch(object, source);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should compare a variety of `source` property values', function(assert) {
+      assert.expect(2);
+
+      var object1 = { 'a': false, 'b': true, 'c': '3', 'd': 4, 'e': [5], 'f': { 'g': 6 } },
+          object2 = { 'a': 0, 'b': 1, 'c': 3, 'd': '4', 'e': ['5'], 'f': { 'g': '6' } };
+
+      assert.strictEqual(_.isMatch(object1, object1), true);
+      assert.strictEqual(_.isMatch(object1, object2), false);
+    });
+
+    QUnit.test('should match `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      var object1 = { 'a': -0 },
+          object2 = { 'a': 0 };
+
+      assert.strictEqual(_.isMatch(object1, object2), true);
+      assert.strictEqual(_.isMatch(object2, object1), true);
+    });
+
+    QUnit.test('should compare functions by reference', function(assert) {
+      assert.expect(3);
+
+      var object1 = { 'a': lodashStable.noop },
+          object2 = { 'a': noop },
+          object3 = { 'a': {} };
+
+      assert.strictEqual(_.isMatch(object1, object1), true);
+      assert.strictEqual(_.isMatch(object2, object1), false);
+      assert.strictEqual(_.isMatch(object3, object1), false);
+    });
+
+    QUnit.test('should work with a function for `object`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.a = { 'b': 1, 'c': 2 };
+
+      assert.strictEqual(_.isMatch(Foo, { 'a': { 'b': 1 } }), true);
+    });
+
+    QUnit.test('should work with a function for `source`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.a = 1;
+      Foo.b = function() {};
+      Foo.c = 3;
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': Foo.b, 'c': 3 }];
+
+      var actual = lodashStable.map(objects, function(object) {
+        return _.isMatch(object, Foo);
+      });
+
+      assert.deepEqual(actual, [false, true]);
+    });
+
+    QUnit.test('should work with a non-plain `object`', function(assert) {
+      assert.expect(1);
+
+      function Foo(object) { lodashStable.assign(this, object); }
+
+      var object = new Foo({ 'a': new Foo({ 'b': 1, 'c': 2 }) });
+      assert.strictEqual(_.isMatch(object, { 'a': { 'b': 1 } }), true);
+    });
+
+    QUnit.test('should partial match arrays', function(assert) {
+      assert.expect(3);
+
+      var objects = [{ 'a': ['b'] }, { 'a': ['c', 'd'] }],
+          source = { 'a': ['d'] },
+          predicate = function(object) { return _.isMatch(object, source); },
+          actual = lodashStable.filter(objects, predicate);
+
+      assert.deepEqual(actual, [objects[1]]);
+
+      source = { 'a': ['b', 'd'] };
+      actual = lodashStable.filter(objects, predicate);
+
+      assert.deepEqual(actual, []);
+
+      source = { 'a': ['d', 'b'] };
+      actual = lodashStable.filter(objects, predicate);
+      assert.deepEqual(actual, []);
+    });
+
+    QUnit.test('should partial match arrays of objects', function(assert) {
+      assert.expect(1);
+
+      var source = { 'a': [{ 'b': 1 }, { 'b': 4, 'c': 5 }] };
+
+      var objects = [
+        { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 5, 'd': 6 }] },
+        { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 6, 'd': 7 }] }
+      ];
+
+      var actual = lodashStable.filter(objects, function(object) {
+        return _.isMatch(object, source);
+      });
+
+      assert.deepEqual(actual, [objects[0]]);
+    });
+
+    QUnit.test('should partial match maps', function(assert) {
+      assert.expect(3);
+
+      if (Map) {
+        var objects = [{ 'a': new Map }, { 'a': new Map }];
+        objects[0].a.set('a', 1);
+        objects[1].a.set('a', 1);
+        objects[1].a.set('b', 2);
+
+        var map = new Map;
+        map.set('b', 2);
+
+        var source = { 'a': map },
+            predicate = function(object) { return _.isMatch(object, source); },
+            actual = lodashStable.filter(objects, predicate);
+
+        assert.deepEqual(actual, [objects[1]]);
+
+        map['delete']('b');
+        actual = lodashStable.filter(objects, predicate);
+
+        assert.deepEqual(actual, objects);
+
+        map.set('c', 3);
+        actual = lodashStable.filter(objects, predicate);
+
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should partial match sets', function(assert) {
+      assert.expect(3);
+
+      if (Set) {
+        var objects = [{ 'a': new Set }, { 'a': new Set }];
+        objects[0].a.add(1);
+        objects[1].a.add(1);
+        objects[1].a.add(2);
+
+        var set = new Set;
+        set.add(2);
+
+        var source = { 'a': set },
+            predicate = function(object) { return _.isMatch(object, source); },
+            actual = lodashStable.filter(objects, predicate);
+
+        assert.deepEqual(actual, [objects[1]]);
+
+        set['delete'](2);
+        actual = lodashStable.filter(objects, predicate);
+
+        assert.deepEqual(actual, objects);
+
+        set.add(3);
+        actual = lodashStable.filter(objects, predicate);
+
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should match `undefined` values', function(assert) {
+      assert.expect(3);
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }],
+          source = { 'b': undefined },
+          predicate = function(object) { return _.isMatch(object, source); },
+          actual = lodashStable.map(objects, predicate),
+          expected = [false, false, true];
+
+      assert.deepEqual(actual, expected);
+
+      source = { 'a': 1, 'b': undefined };
+      actual = lodashStable.map(objects, predicate);
+
+      assert.deepEqual(actual, expected);
+
+      objects = [{ 'a': { 'b': 1 } }, { 'a': { 'b': 1, 'c': 1 } }, { 'a': { 'b': 1, 'c': undefined } }];
+      source = { 'a': { 'c': undefined } };
+      actual = lodashStable.map(objects, predicate);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should match `undefined` values on primitives', function(assert) {
+      assert.expect(3);
+
+      numberProto.a = 1;
+      numberProto.b = undefined;
+
+      try {
+        assert.strictEqual(_.isMatch(1, { 'b': undefined }), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      try {
+        assert.strictEqual(_.isMatch(1, { 'a': 1, 'b': undefined }), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      numberProto.a = { 'b': 1, 'c': undefined };
+      try {
+        assert.strictEqual(_.isMatch(1, { 'a': { 'c': undefined } }), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      delete numberProto.a;
+      delete numberProto.b;
+    });
+
+    QUnit.test('should return `false` when `object` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [null, undefined],
+          expected = lodashStable.map(values, alwaysFalse),
+          source = { 'a': 1 };
+
+      var actual = lodashStable.map(values, function(value) {
+        try {
+          return _.isMatch(value, source);
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing an empty `source` to a nullish `object`', function(assert) {
+      assert.expect(1);
+
+      var values = [null, undefined],
+          expected = lodashStable.map(values, alwaysTrue),
+          source = {};
+
+      var actual = lodashStable.map(values, function(value) {
+        try {
+          return _.isMatch(value, source);
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing an empty `source`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1 },
+          expected = lodashStable.map(empties, alwaysTrue);
+
+      var actual = lodashStable.map(empties, function(value) {
+        return _.isMatch(object, value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing a `source` of empty arrays and objects', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': [1], 'b': { 'c': 1 } }, { 'a': [2, 3], 'b': { 'd': 2 } }],
+          source = { 'a': [], 'b': {} };
+
+      var actual = lodashStable.filter(objects, function(object) {
+        return _.isMatch(object, source);
+      });
+
+      assert.deepEqual(actual, objects);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isMatchWith');
+
+  (function() {
+    QUnit.test('should provide the correct `customizer` arguments', function(assert) {
+      assert.expect(1);
+
+      var argsList = [],
+          object1 = { 'a': [1, 2], 'b': null },
+          object2 = { 'a': [1, 2], 'b': null };
+
+      object1.b = object2;
+      object2.b = object1;
+
+      var expected = [
+        [object1.a, object2.a, 'a', object1, object2],
+        [object1.a[0], object2.a[0], 0, object1.a, object2.a],
+        [object1.a[1], object2.a[1], 1, object1.a, object2.a],
+        [object1.b, object2.b, 'b', object1, object2],
+        [object1.b.a, object2.b.a, 'a', object1.b, object2.b],
+        [object1.b.a[0], object2.b.a[0], 0, object1.b.a, object2.b.a],
+        [object1.b.a[1], object2.b.a[1], 1, object1.b.a, object2.b.a],
+        [object1.b.b, object2.b.b, 'b', object1.b, object2.b],
+        [object1.b.b.a, object2.b.b.a, 'a', object1.b.b, object2.b.b],
+        [object1.b.b.a[0], object2.b.b.a[0], 0, object1.b.b.a, object2.b.b.a],
+        [object1.b.b.a[1], object2.b.b.a[1], 1, object1.b.b.a, object2.b.b.a],
+        [object1.b.b.b, object2.b.b.b, 'b', object1.b.b, object2.b.b]
+      ];
+
+      _.isMatchWith(object1, object2, function(assert) {
+        argsList.push(slice.call(arguments, 0, -1));
+      });
+
+      assert.deepEqual(argsList, expected);
+    });
+
+    QUnit.test('should handle comparisons if `customizer` returns `undefined`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isMatchWith({ 'a': 1 }, { 'a': 1 }, noop), true);
+    });
+
+    QUnit.test('should not handle comparisons if `customizer` returns `true`', function(assert) {
+      assert.expect(2);
+
+      var customizer = function(value) {
+        return _.isString(value) || undefined;
+      };
+
+      assert.strictEqual(_.isMatchWith(['a'], ['b'], customizer), true);
+      assert.strictEqual(_.isMatchWith({ '0': 'a' }, { '0': 'b' }, customizer), true);
+    });
+
+    QUnit.test('should not handle comparisons if `customizer` returns `false`', function(assert) {
+      assert.expect(2);
+
+      var customizer = function(value) {
+        return _.isString(value) ? false : undefined;
+      };
+
+      assert.strictEqual(_.isMatchWith(['a'], ['a'], customizer), false);
+      assert.strictEqual(_.isMatchWith({ '0': 'a' }, { '0': 'a' }, customizer), false);
+    });
+
+    QUnit.test('should return a boolean value even if `customizer` does not', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1 },
+          actual = _.isMatchWith(object, { 'a': 1 }, alwaysA);
+
+      assert.strictEqual(actual, true);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      actual = [];
+      lodashStable.each(falsey, function(value) {
+        actual.push(_.isMatchWith(object, { 'a': 2 }, lodashStable.constant(value)));
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should ensure `customizer` is a function', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1 },
+          matches = _.partial(_.isMatchWith, object),
+          actual = lodashStable.map([object, { 'a': 2 }], matches);
+
+      assert.deepEqual(actual, [true, false]);
+    });
+
+    QUnit.test('should call `customizer` for values maps and sets', function(assert) {
+      assert.expect(2);
+
+      var value = { 'a': { 'b': 2 } };
+
+      if (Map) {
+        var map1 = new Map;
+        map1.set('a', value);
+
+        var map2 = new Map;
+        map2.set('a', value);
+      }
+      if (Set) {
+        var set1 = new Set;
+        set1.add(value);
+
+        var set2 = new Set;
+        set2.add(value);
+      }
+      lodashStable.each([[map1, map2], [set1, set2]], function(pair, index) {
+        if (pair[0]) {
+          var argsList = [],
+              array = _.toArray(pair[0]),
+              object1 = { 'a': pair[0] },
+              object2 = { 'a': pair[1] };
+
+          var expected = [
+            [pair[0], pair[1], 'a', object1, object2],
+            [array[0], array[0], 0, array, array],
+            [array[0][0], array[0][0], 0, array[0], array[0]],
+            [array[0][1], array[0][1], 1, array[0], array[0]]
+          ];
+
+          if (index) {
+            expected.length = 2;
+          }
+          _.isMatchWith({ 'a': pair[0] }, { 'a': pair[1] }, function() {
+            argsList.push(slice.call(arguments, 0, -1));
+          });
+
+          assert.deepEqual(argsList, expected, index ? 'Set' : 'Map');
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isNaN');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for NaNs', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isNaN(NaN), true);
+      assert.strictEqual(_.isNaN(Object(NaN)), true);
+    });
+
+    QUnit.test('should return `false` for non-NaNs', function(assert) {
+      assert.expect(14);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value !== value;
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isNaN(value) : _.isNaN();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isNaN(args), false);
+      assert.strictEqual(_.isNaN([1, 2, 3]), false);
+      assert.strictEqual(_.isNaN(true), false);
+      assert.strictEqual(_.isNaN(new Date), false);
+      assert.strictEqual(_.isNaN(new Error), false);
+      assert.strictEqual(_.isNaN(_), false);
+      assert.strictEqual(_.isNaN(slice), false);
+      assert.strictEqual(_.isNaN({ 'a': 1 }), false);
+      assert.strictEqual(_.isNaN(1), false);
+      assert.strictEqual(_.isNaN(Object(1)), false);
+      assert.strictEqual(_.isNaN(/x/), false);
+      assert.strictEqual(_.isNaN('a'), false);
+      assert.strictEqual(_.isNaN(symbol), false);
+    });
+
+    QUnit.test('should work with `NaN` from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.object) {
+        assert.strictEqual(_.isNaN(realm.nan), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isNative');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for native methods', function(assert) {
+      assert.expect(1);
+
+      var values = [Array, body && body.cloneNode, create, root.encodeURI, Promise, slice, Uint8Array],
+          expected = lodashStable.map(values, Boolean),
+          actual = lodashStable.map(values, _.isNative);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non-native methods', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isNative(value) : _.isNative();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isNative(args), false);
+      assert.strictEqual(_.isNative([1, 2, 3]), false);
+      assert.strictEqual(_.isNative(true), false);
+      assert.strictEqual(_.isNative(new Date), false);
+      assert.strictEqual(_.isNative(new Error), false);
+      assert.strictEqual(_.isNative(_), false);
+      assert.strictEqual(_.isNative({ 'a': 1 }), false);
+      assert.strictEqual(_.isNative(1), false);
+      assert.strictEqual(_.isNative(/x/), false);
+      assert.strictEqual(_.isNative('a'), false);
+      assert.strictEqual(_.isNative(symbol), false);
+    });
+
+    QUnit.test('should work with native functions from another realm', function(assert) {
+      assert.expect(2);
+
+      if (realm.element) {
+        assert.strictEqual(_.isNative(realm.element.cloneNode), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+      if (realm.object) {
+        assert.strictEqual(_.isNative(realm.object.valueOf), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isNil');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for nullish values', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.isNil(null), true);
+      assert.strictEqual(_.isNil(), true);
+      assert.strictEqual(_.isNil(undefined), true);
+    });
+
+    QUnit.test('should return `false` for non-nullish values', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value == null;
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isNil(value) : _.isNil();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isNil(args), false);
+      assert.strictEqual(_.isNil([1, 2, 3]), false);
+      assert.strictEqual(_.isNil(true), false);
+      assert.strictEqual(_.isNil(new Date), false);
+      assert.strictEqual(_.isNil(new Error), false);
+      assert.strictEqual(_.isNil(_), false);
+      assert.strictEqual(_.isNil(slice), false);
+      assert.strictEqual(_.isNil({ 'a': 1 }), false);
+      assert.strictEqual(_.isNil(1), false);
+      assert.strictEqual(_.isNil(/x/), false);
+      assert.strictEqual(_.isNil('a'), false);
+
+      if (Symbol) {
+        assert.strictEqual(_.isNil(symbol), false);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work with nulls from another realm', function(assert) {
+      assert.expect(2);
+
+      if (realm.object) {
+        assert.strictEqual(_.isNil(realm.null), true);
+        assert.strictEqual(_.isNil(realm.undefined), true);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isNull');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for `null` values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isNull(null), true);
+    });
+
+    QUnit.test('should return `false` for non `null` values', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === null;
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isNull(value) : _.isNull();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isNull(args), false);
+      assert.strictEqual(_.isNull([1, 2, 3]), false);
+      assert.strictEqual(_.isNull(true), false);
+      assert.strictEqual(_.isNull(new Date), false);
+      assert.strictEqual(_.isNull(new Error), false);
+      assert.strictEqual(_.isNull(_), false);
+      assert.strictEqual(_.isNull(slice), false);
+      assert.strictEqual(_.isNull({ 'a': 1 }), false);
+      assert.strictEqual(_.isNull(1), false);
+      assert.strictEqual(_.isNull(/x/), false);
+      assert.strictEqual(_.isNull('a'), false);
+      assert.strictEqual(_.isNull(symbol), false);
+    });
+
+    QUnit.test('should work with nulls from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.object) {
+        assert.strictEqual(_.isNull(realm.null), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isNumber');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.isNumber(0), true);
+      assert.strictEqual(_.isNumber(Object(0)), true);
+      assert.strictEqual(_.isNumber(NaN), true);
+    });
+
+    QUnit.test('should return `false` for non-numbers', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return typeof value == 'number';
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isNumber(value) : _.isNumber();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isNumber(args), false);
+      assert.strictEqual(_.isNumber([1, 2, 3]), false);
+      assert.strictEqual(_.isNumber(true), false);
+      assert.strictEqual(_.isNumber(new Date), false);
+      assert.strictEqual(_.isNumber(new Error), false);
+      assert.strictEqual(_.isNumber(_), false);
+      assert.strictEqual(_.isNumber(slice), false);
+      assert.strictEqual(_.isNumber({ 'a': 1 }), false);
+      assert.strictEqual(_.isNumber(/x/), false);
+      assert.strictEqual(_.isNumber('a'), false);
+      assert.strictEqual(_.isNumber(symbol), false);
+    });
+
+    QUnit.test('should work with numbers from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.number) {
+        assert.strictEqual(_.isNumber(realm.number), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should avoid `[xpconnect wrapped native prototype]` in Firefox', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.isNumber(+'2'), true);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isObject');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for objects', function(assert) {
+      assert.expect(13);
+
+      assert.strictEqual(_.isObject(args), true);
+      assert.strictEqual(_.isObject([1, 2, 3]), true);
+      assert.strictEqual(_.isObject(Object(false)), true);
+      assert.strictEqual(_.isObject(new Date), true);
+      assert.strictEqual(_.isObject(new Error), true);
+      assert.strictEqual(_.isObject(_), true);
+      assert.strictEqual(_.isObject(slice), true);
+      assert.strictEqual(_.isObject({ 'a': 1 }), true);
+      assert.strictEqual(_.isObject(Object(0)), true);
+      assert.strictEqual(_.isObject(/x/), true);
+      assert.strictEqual(_.isObject(Object('a')), true);
+
+      if (document) {
+        assert.strictEqual(_.isObject(body), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+      if (Symbol) {
+        assert.strictEqual(_.isObject(Object(symbol)), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non-objects', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat(true, 1, 'a', symbol),
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.isObject(value) : _.isObject();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with objects from another realm', function(assert) {
+      assert.expect(8);
+
+      if (realm.element) {
+        assert.strictEqual(_.isObject(realm.element), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+      if (realm.object) {
+        assert.strictEqual(_.isObject(realm.boolean), true);
+        assert.strictEqual(_.isObject(realm.date), true);
+        assert.strictEqual(_.isObject(realm.function), true);
+        assert.strictEqual(_.isObject(realm.number), true);
+        assert.strictEqual(_.isObject(realm.object), true);
+        assert.strictEqual(_.isObject(realm.regexp), true);
+        assert.strictEqual(_.isObject(realm.string), true);
+      }
+      else {
+        skipAssert(assert, 7);
+      }
+    });
+
+    QUnit.test('should avoid V8 bug #2291 (test in Chrome 19-20)', function(assert) {
+      assert.expect(1);
+
+      // Trigger a V8 JIT bug.
+      // See https://code.google.com/p/v8/issues/detail?id=2291.
+      var object = {};
+
+      // First, have a comparison statement.
+      object == object;
+
+      // Then perform the check with `object`.
+      _.isObject(object);
+
+      assert.strictEqual(_.isObject('a'), false);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isObjectLike');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for objects', function(assert) {
+      assert.expect(9);
+
+      assert.strictEqual(_.isObjectLike(args), true);
+      assert.strictEqual(_.isObjectLike([1, 2, 3]), true);
+      assert.strictEqual(_.isObjectLike(Object(false)), true);
+      assert.strictEqual(_.isObjectLike(new Date), true);
+      assert.strictEqual(_.isObjectLike(new Error), true);
+      assert.strictEqual(_.isObjectLike({ 'a': 1 }), true);
+      assert.strictEqual(_.isObjectLike(Object(0)), true);
+      assert.strictEqual(_.isObjectLike(/x/), true);
+      assert.strictEqual(_.isObjectLike(Object('a')), true);
+    });
+
+    QUnit.test('should return `false` for non-objects', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat(true, _, slice, 1, 'a', symbol),
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.isObjectLike(value) : _.isObjectLike();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with objects from another realm', function(assert) {
+      assert.expect(6);
+
+      if (realm.object) {
+        assert.strictEqual(_.isObjectLike(realm.boolean), true);
+        assert.strictEqual(_.isObjectLike(realm.date), true);
+        assert.strictEqual(_.isObjectLike(realm.number), true);
+        assert.strictEqual(_.isObjectLike(realm.object), true);
+        assert.strictEqual(_.isObjectLike(realm.regexp), true);
+        assert.strictEqual(_.isObjectLike(realm.string), true);
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isPlainObject');
+
+  (function() {
+    var element = document && document.createElement('div');
+
+    QUnit.test('should detect plain objects', function(assert) {
+      assert.expect(5);
+
+      function Foo(a) {
+        this.a = 1;
+      }
+
+      assert.strictEqual(_.isPlainObject({}), true);
+      assert.strictEqual(_.isPlainObject({ 'a': 1 }), true);
+      assert.strictEqual(_.isPlainObject({ 'constructor': Foo }), true);
+      assert.strictEqual(_.isPlainObject([1, 2, 3]), false);
+      assert.strictEqual(_.isPlainObject(new Foo(1)), false);
+    });
+
+    QUnit.test('should return `true` for objects with a `[[Prototype]]` of `null`', function(assert) {
+      assert.expect(2);
+
+      if (create) {
+        var object = create(null);
+        assert.strictEqual(_.isPlainObject(object), true);
+
+        object.constructor = objectProto.constructor;
+        assert.strictEqual(_.isPlainObject(object), true);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should return `true` for plain objects with a custom `valueOf` property', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isPlainObject({ 'valueOf': 0 }), true);
+
+      if (element) {
+        var valueOf = element.valueOf;
+        element.valueOf = 0;
+
+        assert.strictEqual(_.isPlainObject(element), false);
+        element.valueOf = valueOf;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for objects with a custom `[[Prototype]]`', function(assert) {
+      assert.expect(1);
+
+      if (create) {
+        var object = create({ 'a': 1 });
+        assert.strictEqual(_.isPlainObject(object), false);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for DOM elements', function(assert) {
+      assert.expect(1);
+
+      if (element) {
+        assert.strictEqual(_.isPlainObject(element), false);
+      } else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for Object objects without a `toStringTag` of "Object"', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.isPlainObject(arguments), false);
+      assert.strictEqual(_.isPlainObject(Error), false);
+      assert.strictEqual(_.isPlainObject(Math), false);
+    });
+
+    QUnit.test('should return `false` for non-objects', function(assert) {
+      assert.expect(4);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isPlainObject(value) : _.isPlainObject();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isPlainObject(true), false);
+      assert.strictEqual(_.isPlainObject('a'), false);
+      assert.strictEqual(_.isPlainObject(symbol), false);
+    });
+
+    QUnit.test('should work with objects from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.object) {
+        assert.strictEqual(_.isPlainObject(realm.object), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isRegExp');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for regexes', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isRegExp(/x/), true);
+      assert.strictEqual(_.isRegExp(RegExp('x')), true);
+    });
+
+    QUnit.test('should return `false` for non-regexes', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isRegExp(value) : _.isRegExp();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isRegExp(args), false);
+      assert.strictEqual(_.isRegExp([1, 2, 3]), false);
+      assert.strictEqual(_.isRegExp(true), false);
+      assert.strictEqual(_.isRegExp(new Date), false);
+      assert.strictEqual(_.isRegExp(new Error), false);
+      assert.strictEqual(_.isRegExp(_), false);
+      assert.strictEqual(_.isRegExp(slice), false);
+      assert.strictEqual(_.isRegExp({ 'a': 1 }), false);
+      assert.strictEqual(_.isRegExp(1), false);
+      assert.strictEqual(_.isRegExp('a'), false);
+      assert.strictEqual(_.isRegExp(symbol), false);
+    });
+
+    QUnit.test('should work with regexes from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.regexp) {
+        assert.strictEqual(_.isRegExp(realm.regexp), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isSet');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for sets', function(assert) {
+      assert.expect(1);
+
+      if (Set) {
+        assert.strictEqual(_.isSet(set), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non-sets', function(assert) {
+      assert.expect(14);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isSet(value) : _.isSet();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isSet(args), false);
+      assert.strictEqual(_.isSet([1, 2, 3]), false);
+      assert.strictEqual(_.isSet(true), false);
+      assert.strictEqual(_.isSet(new Date), false);
+      assert.strictEqual(_.isSet(new Error), false);
+      assert.strictEqual(_.isSet(_), false);
+      assert.strictEqual(_.isSet(slice), false);
+      assert.strictEqual(_.isSet({ 'a': 1 }), false);
+      assert.strictEqual(_.isSet(1), false);
+      assert.strictEqual(_.isSet(/x/), false);
+      assert.strictEqual(_.isSet('a'), false);
+      assert.strictEqual(_.isSet(symbol), false);
+      assert.strictEqual(_.isSet(weakSet), false);
+    });
+
+    QUnit.test('should work for objects with a non-function `constructor` (test in IE 11)', function(assert) {
+      assert.expect(1);
+
+      var values = [false, true],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.isSet({ 'constructor': value });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with weak sets from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.set) {
+        assert.strictEqual(_.isSet(realm.set), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isString');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for strings', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isString('a'), true);
+      assert.strictEqual(_.isString(Object('a')), true);
+    });
+
+    QUnit.test('should return `false` for non-strings', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === '';
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isString(value) : _.isString();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isString(args), false);
+      assert.strictEqual(_.isString([1, 2, 3]), false);
+      assert.strictEqual(_.isString(true), false);
+      assert.strictEqual(_.isString(new Date), false);
+      assert.strictEqual(_.isString(new Error), false);
+      assert.strictEqual(_.isString(_), false);
+      assert.strictEqual(_.isString(slice), false);
+      assert.strictEqual(_.isString({ '0': 1, 'length': 1 }), false);
+      assert.strictEqual(_.isString(1), false);
+      assert.strictEqual(_.isString(/x/), false);
+      assert.strictEqual(_.isString(symbol), false);
+    });
+
+    QUnit.test('should work with strings from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.string) {
+        assert.strictEqual(_.isString(realm.string), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isSymbol');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for symbols', function(assert) {
+      assert.expect(2);
+
+      if (Symbol) {
+        assert.strictEqual(_.isSymbol(symbol), true);
+        assert.strictEqual(_.isSymbol(Object(symbol)), true);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should return `false` for non-symbols', function(assert) {
+      assert.expect(12);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isSymbol(value) : _.isSymbol();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isSymbol(args), false);
+      assert.strictEqual(_.isSymbol([1, 2, 3]), false);
+      assert.strictEqual(_.isSymbol(true), false);
+      assert.strictEqual(_.isSymbol(new Date), false);
+      assert.strictEqual(_.isSymbol(new Error), false);
+      assert.strictEqual(_.isSymbol(_), false);
+      assert.strictEqual(_.isSymbol(slice), false);
+      assert.strictEqual(_.isSymbol({ '0': 1, 'length': 1 }), false);
+      assert.strictEqual(_.isSymbol(1), false);
+      assert.strictEqual(_.isSymbol(/x/), false);
+      assert.strictEqual(_.isSymbol('a'), false);
+    });
+
+    QUnit.test('should work with symbols from another realm', function(assert) {
+      assert.expect(1);
+
+      if (Symbol && realm.symbol) {
+        assert.strictEqual(_.isSymbol(realm.symbol), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isTypedArray');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for typed arrays', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(typedArrays, function(type) {
+        return type in root;
+      });
+
+      var actual = lodashStable.map(typedArrays, function(type) {
+        var Ctor = root[type];
+        return Ctor ? _.isTypedArray(new Ctor(new ArrayBuffer(8))) : false;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `false` for non typed arrays', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isTypedArray(value) : _.isTypedArray();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isTypedArray(args), false);
+      assert.strictEqual(_.isTypedArray([1, 2, 3]), false);
+      assert.strictEqual(_.isTypedArray(true), false);
+      assert.strictEqual(_.isTypedArray(new Date), false);
+      assert.strictEqual(_.isTypedArray(new Error), false);
+      assert.strictEqual(_.isTypedArray(_), false);
+      assert.strictEqual(_.isTypedArray(slice), false);
+      assert.strictEqual(_.isTypedArray({ 'a': 1 }), false);
+      assert.strictEqual(_.isTypedArray(1), false);
+      assert.strictEqual(_.isTypedArray(/x/), false);
+      assert.strictEqual(_.isTypedArray('a'), false);
+      assert.strictEqual(_.isTypedArray(symbol), false);
+    });
+
+    QUnit.test('should work with typed arrays from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.object) {
+        var props = lodashStable.invokeMap(typedArrays, 'toLowerCase');
+
+        var expected = lodashStable.map(props, function(key) {
+          return realm[key] !== undefined;
+        });
+
+        var actual = lodashStable.map(props, function(key) {
+          var value = realm[key];
+          return value ? _.isTypedArray(value) : false;
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isUndefined');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for `undefined` values', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.isUndefined(), true);
+      assert.strictEqual(_.isUndefined(undefined), true);
+    });
+
+    QUnit.test('should return `false` for non `undefined` values', function(assert) {
+      assert.expect(13);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined;
+      });
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isUndefined(value) : _.isUndefined();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isUndefined(args), false);
+      assert.strictEqual(_.isUndefined([1, 2, 3]), false);
+      assert.strictEqual(_.isUndefined(true), false);
+      assert.strictEqual(_.isUndefined(new Date), false);
+      assert.strictEqual(_.isUndefined(new Error), false);
+      assert.strictEqual(_.isUndefined(_), false);
+      assert.strictEqual(_.isUndefined(slice), false);
+      assert.strictEqual(_.isUndefined({ 'a': 1 }), false);
+      assert.strictEqual(_.isUndefined(1), false);
+      assert.strictEqual(_.isUndefined(/x/), false);
+      assert.strictEqual(_.isUndefined('a'), false);
+
+      if (Symbol) {
+        assert.strictEqual(_.isUndefined(symbol), false);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work with `undefined` from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.object) {
+        assert.strictEqual(_.isUndefined(realm.undefined), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isWeakMap');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for weak maps', function(assert) {
+      assert.expect(1);
+
+      if (WeakMap) {
+        assert.strictEqual(_.isWeakMap(weakMap), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non weak maps', function(assert) {
+      assert.expect(14);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isWeakMap(value) : _.isWeakMap();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isWeakMap(args), false);
+      assert.strictEqual(_.isWeakMap([1, 2, 3]), false);
+      assert.strictEqual(_.isWeakMap(true), false);
+      assert.strictEqual(_.isWeakMap(new Date), false);
+      assert.strictEqual(_.isWeakMap(new Error), false);
+      assert.strictEqual(_.isWeakMap(_), false);
+      assert.strictEqual(_.isWeakMap(slice), false);
+      assert.strictEqual(_.isWeakMap({ 'a': 1 }), false);
+      assert.strictEqual(_.isWeakMap(map), false);
+      assert.strictEqual(_.isWeakMap(1), false);
+      assert.strictEqual(_.isWeakMap(/x/), false);
+      assert.strictEqual(_.isWeakMap('a'), false);
+      assert.strictEqual(_.isWeakMap(symbol), false);
+    });
+
+    QUnit.test('should work for objects with a non-function `constructor` (test in IE 11)', function(assert) {
+      assert.expect(1);
+
+      var values = [false, true],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.isWeakMap({ 'constructor': value });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with weak maps from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.weakMap) {
+        assert.strictEqual(_.isWeakMap(realm.weakMap), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.isWeakSet');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should return `true` for weak sets', function(assert) {
+      assert.expect(1);
+
+      if (WeakSet) {
+        assert.strictEqual(_.isWeakSet(weakSet), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return `false` for non weak sets', function(assert) {
+      assert.expect(14);
+
+      var expected = lodashStable.map(falsey, alwaysFalse);
+
+      var actual = lodashStable.map(falsey, function(value, index) {
+        return index ? _.isWeakSet(value) : _.isWeakSet();
+      });
+
+      assert.deepEqual(actual, expected);
+
+      assert.strictEqual(_.isWeakSet(args), false);
+      assert.strictEqual(_.isWeakSet([1, 2, 3]), false);
+      assert.strictEqual(_.isWeakSet(true), false);
+      assert.strictEqual(_.isWeakSet(new Date), false);
+      assert.strictEqual(_.isWeakSet(new Error), false);
+      assert.strictEqual(_.isWeakSet(_), false);
+      assert.strictEqual(_.isWeakSet(slice), false);
+      assert.strictEqual(_.isWeakSet({ 'a': 1 }), false);
+      assert.strictEqual(_.isWeakSet(1), false);
+      assert.strictEqual(_.isWeakSet(/x/), false);
+      assert.strictEqual(_.isWeakSet('a'), false);
+      assert.strictEqual(_.isWeakSet(set), false);
+      assert.strictEqual(_.isWeakSet(symbol), false);
+    });
+
+    QUnit.test('should work with weak sets from another realm', function(assert) {
+      assert.expect(1);
+
+      if (realm.weakSet) {
+        assert.strictEqual(_.isWeakSet(realm.weakSet), true);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('isType checks');
+
+  (function() {
+    QUnit.test('should return `false` for subclassed values', function(assert) {
+      assert.expect(7);
+
+      var funcs = [
+        'isArray', 'isBoolean', 'isDate', 'isFunction',
+        'isNumber', 'isRegExp', 'isString'
+      ];
+
+      lodashStable.each(funcs, function(methodName) {
+        function Foo() {}
+        Foo.prototype = root[methodName.slice(2)].prototype;
+
+        var object = new Foo;
+        if (objToString.call(object) == objectTag) {
+          assert.strictEqual(_[methodName](object), false, '`_.' + methodName + '` returns `false`');
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    QUnit.test('should not error on host objects (test in IE)', function(assert) {
+      assert.expect(26);
+
+      var funcs = [
+        'isArguments', 'isArray', 'isArrayBuffer', 'isArrayLike', 'isBoolean',
+        'isBuffer', 'isDate', 'isElement', 'isError', 'isFinite', 'isFunction',
+        'isInteger', 'isMap', 'isNaN', 'isNil', 'isNull', 'isNumber', 'isObject',
+        'isObjectLike', 'isRegExp', 'isSet', 'isSafeInteger', 'isString',
+        'isUndefined', 'isWeakMap', 'isWeakSet'
+      ];
+
+      lodashStable.each(funcs, function(methodName) {
+        if (xml) {
+          var pass = true;
+
+          try {
+            _[methodName](xml);
+          } catch (e) {
+            pass = false;
+          }
+          assert.ok(pass, '`_.' + methodName + '` should not error');
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.iteratee');
+
+  (function() {
+    QUnit.test('should provide arguments to `func`', function(assert) {
+      assert.expect(1);
+
+      var fn = function() { return slice.call(arguments); },
+          iteratee = _.iteratee(fn),
+          actual = iteratee('a', 'b', 'c', 'd', 'e', 'f');
+
+      assert.deepEqual(actual, ['a', 'b', 'c', 'd', 'e', 'f']);
+    });
+
+    QUnit.test('should return `_.identity` when `func` is nullish', function(assert) {
+      assert.expect(1);
+
+      var object = {},
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([!isNpm && _.identity, object]));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var identity = index ? _.iteratee(value) : _.iteratee();
+        return [!isNpm && identity, identity(object)];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an iteratee created by `_.matches` when `func` is an object', function(assert) {
+      assert.expect(2);
+
+      var matches = _.iteratee({ 'a': 1, 'b': 2 });
+      assert.strictEqual(matches({ 'a': 1, 'b': 2, 'c': 3 }), true);
+      assert.strictEqual(matches({ 'b': 2 }), false);
+    });
+
+    QUnit.test('should not change `_.matches` behavior if `source` is modified', function(assert) {
+      assert.expect(9);
+
+      var sources = [
+        { 'a': { 'b': 2, 'c': 3 } },
+        { 'a': 1, 'b': 2 },
+        { 'a': 1 }
+      ];
+
+      lodashStable.each(sources, function(source, index) {
+        var object = lodashStable.cloneDeep(source),
+            matches = _.iteratee(source);
+
+        assert.strictEqual(matches(object), true);
+
+        if (index) {
+          source.a = 2;
+          source.b = 1;
+          source.c = 3;
+        } else {
+          source.a.b = 1;
+          source.a.c = 2;
+          source.a.d = 3;
+        }
+        assert.strictEqual(matches(object), true);
+        assert.strictEqual(matches(source), false);
+      });
+    });
+
+    QUnit.test('should return an iteratee created by `_.matchesProperty` when `func` is an array', function(assert) {
+      assert.expect(3);
+
+      var array = ['a', undefined],
+          matches = _.iteratee([0, 'a']);
+
+      assert.strictEqual(matches(array), true);
+
+      matches = _.iteratee(['0', 'a']);
+      assert.strictEqual(matches(array), true);
+
+      matches = _.iteratee([1, undefined]);
+      assert.strictEqual(matches(array), true);
+    });
+
+    QUnit.test('should support deep paths for `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': { 'c': 1, 'd': 2 } } },
+          matches = _.iteratee(['a.b', { 'c': 1 }]);
+
+      assert.strictEqual(matches(object), true);
+    });
+
+    QUnit.test('should not change `_.matchesProperty` behavior if `source` is modified', function(assert) {
+      assert.expect(9);
+
+      var sources = [
+        { 'a': { 'b': 2, 'c': 3 } },
+        { 'a': 1, 'b': 2 },
+        { 'a': 1 }
+      ];
+
+      lodashStable.each(sources, function(source, index) {
+        var object = { 'a': lodashStable.cloneDeep(source) },
+            matches = _.iteratee(['a', source]);
+
+        assert.strictEqual(matches(object), true);
+
+        if (index) {
+          source.a = 2;
+          source.b = 1;
+          source.c = 3;
+        } else {
+          source.a.b = 1;
+          source.a.c = 2;
+          source.a.d = 3;
+        }
+        assert.strictEqual(matches(object), true);
+        assert.strictEqual(matches({ 'a': source }), false);
+      });
+    });
+
+    QUnit.test('should return an iteratee created by `_.property` when `func` is a number or string', function(assert) {
+      assert.expect(2);
+
+      var array = ['a'],
+          prop = _.iteratee(0);
+
+      assert.strictEqual(prop(array), 'a');
+
+      prop = _.iteratee('0');
+      assert.strictEqual(prop(array), 'a');
+    });
+
+    QUnit.test('should support deep paths for `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': 2 } },
+          prop = _.iteratee('a.b');
+
+      assert.strictEqual(prop(object), 2);
+    });
+
+    QUnit.test('should work with functions created by `_.partial` and `_.partialRight`', function(assert) {
+      assert.expect(2);
+
+      var fn = function() {
+        var result = [this.a];
+        push.apply(result, arguments);
+        return result;
+      };
+
+      var expected = [1, 2, 3],
+          object = { 'a': 1 , 'iteratee': _.iteratee(_.partial(fn, 2)) };
+
+      assert.deepEqual(object.iteratee(3), expected);
+
+      object.iteratee = _.iteratee(_.partialRight(fn, 3));
+      assert.deepEqual(object.iteratee(2), expected);
+    });
+
+    QUnit.test('should use internal `iteratee` if external is unavailable', function(assert) {
+      assert.expect(1);
+
+      var iteratee = _.iteratee;
+      delete _.iteratee;
+
+      assert.deepEqual(_.map([{ 'a': 1 }], 'a'), [1]);
+
+      _.iteratee = iteratee;
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var fn = function() { return this instanceof Number; },
+          array = [fn, fn, fn],
+          iteratees = lodashStable.map(array, _.iteratee),
+          expected = lodashStable.map(array, alwaysFalse);
+
+      var actual = lodashStable.map(iteratees, function(iteratee) {
+        return iteratee();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('custom `_.iteratee` methods');
+
+  (function() {
+    var array = ['one', 'two', 'three'],
+        getPropA = _.partial(_.property, 'a'),
+        getPropB = _.partial(_.property, 'b'),
+        getLength = _.partial(_.property, 'length'),
+        iteratee = _.iteratee;
+
+    var getSum = function() {
+      return function(result, object) {
+        return result + object.a;
+      };
+    };
+
+    var objects = [
+      { 'a': 0, 'b': 0 },
+      { 'a': 1, 'b': 0 },
+      { 'a': 1, 'b': 1 }
+    ];
+
+    QUnit.test('`_.countBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getLength;
+        assert.deepEqual(_.countBy(array), { '3': 2, '5': 1 });
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.differenceBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.deepEqual(_.differenceBy(objects, [objects[1]]), [objects[0]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.dropRightWhile` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.dropRightWhile(objects), objects.slice(0, 2));
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.dropWhile` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.dropWhile(objects.reverse()).reverse(), objects.reverse().slice(0, 2));
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.every` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.strictEqual(_.every(objects.slice(1)), true);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.filter` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var objects = [{ 'a': 0 }, { 'a': 1 }];
+
+        _.iteratee = getPropA;
+        assert.deepEqual(_.filter(objects), [objects[1]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.find` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.strictEqual(_.find(objects), objects[1]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.findIndex` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.strictEqual(_.findIndex(objects), 1);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.findLast` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.strictEqual(_.findLast(objects), objects[2]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.findLastIndex` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.strictEqual(_.findLastIndex(objects), 2);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.findKey` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.strictEqual(_.findKey(objects), '2');
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.findLastKey` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.strictEqual(_.findLastKey(objects), '2');
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.groupBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getLength;
+        assert.deepEqual(_.groupBy(array), { '3': ['one', 'two'], '5': ['three'] });
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.intersectionBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.deepEqual(_.intersectionBy(objects, [objects[2]]), [objects[1]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.keyBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getLength;
+        assert.deepEqual(_.keyBy(array), { '3': 'two', '5': 'three' });
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.map` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.deepEqual(_.map(objects), [0, 1, 1]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.mapKeys` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.mapKeys({ 'a': { 'b': 1 } }), { '1':  { 'b': 1 } });
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.mapValues` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.mapValues({ 'a': { 'b': 1 } }), { 'a': 1 });
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.maxBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.maxBy(objects), objects[2]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.meanBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.strictEqual(_.meanBy(objects), 2 / 3);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.minBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.minBy(objects), objects[0]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.partition` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var objects = [{ 'a': 1 }, { 'a': 1 }, { 'b': 2 }];
+
+        _.iteratee = getPropA;
+        assert.deepEqual(_.partition(objects), [objects.slice(0, 2), objects.slice(2)]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.pullAllBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.deepEqual(_.pullAllBy(objects.slice(), [{ 'a': 1, 'b': 0 }]), [objects[0]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.reduce` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getSum;
+        assert.strictEqual(_.reduce(objects, undefined, 0), 2);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.reduceRight` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getSum;
+        assert.strictEqual(_.reduceRight(objects, undefined, 0), 2);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.reject` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var objects = [{ 'a': 0 }, { 'a': 1 }];
+
+        _.iteratee = getPropA;
+        assert.deepEqual(_.reject(objects), [objects[0]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.remove` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var objects = [{ 'a': 0 }, { 'a': 1 }];
+
+        _.iteratee = getPropA;
+        _.remove(objects);
+        assert.deepEqual(objects, [{ 'a': 0 }]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.some` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.strictEqual(_.some(objects), true);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.sortBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.deepEqual(_.sortBy(objects.slice().reverse()), [objects[0], objects[2], objects[1]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.sortedIndexBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var objects = [{ 'a': 30 }, { 'a': 50 }];
+
+        _.iteratee = getPropA;
+        assert.strictEqual(_.sortedIndexBy(objects, { 'a': 40 }), 1);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.sortedLastIndexBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var objects = [{ 'a': 30 }, { 'a': 50 }];
+
+        _.iteratee = getPropA;
+        assert.strictEqual(_.sortedLastIndexBy(objects, { 'a': 40 }), 1);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.sumBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.strictEqual(_.sumBy(objects), 1);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.takeRightWhile` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.takeRightWhile(objects), objects.slice(2));
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.takeWhile` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.takeWhile(objects.reverse()), objects.reverse().slice(2));
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.transform` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = function() {
+          return function(result, object) {
+            result.sum += object.a;
+          };
+        };
+
+        assert.deepEqual(_.transform(objects, undefined, { 'sum': 0 }), { 'sum': 2 });
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.uniqBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.uniqBy(objects), [objects[0], objects[2]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.unionBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropB;
+        assert.deepEqual(_.unionBy(objects.slice(0, 1), [objects[2]]), [objects[0], objects[2]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.xorBy` should use `_.iteratee` internally', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        _.iteratee = getPropA;
+        assert.deepEqual(_.xorBy(objects, objects.slice(1)), [objects[0]]);
+        _.iteratee = iteratee;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.join');
+
+  (function() {
+    var array = ['a', 'b', 'c'];
+
+    QUnit.test('should return join all array elements into a string', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.join(array, '~'), 'a~b~c');
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _(array);
+        assert.strictEqual(wrapped.join('~'), 'a~b~c');
+        assert.strictEqual(wrapped.value(), array);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(array).chain().join('~') instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.keyBy');
+
+  (function() {
+    var array = [
+      { 'dir': 'left', 'code': 97 },
+      { 'dir': 'right', 'code': 100 }
+    ];
+
+    QUnit.test('should transform keys by `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'a': { 'dir': 'left', 'code': 97 }, 'd': { 'dir': 'right', 'code': 100 } };
+
+      var actual = _.keyBy(array, function(object) {
+        return String.fromCharCode(object.code);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var array = [4, 6, 6],
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant({ '4': 4, '6': 6 }));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.keyBy(array, value) : _.keyBy(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'left': { 'dir': 'left', 'code': 97 }, 'right': { 'dir': 'right', 'code': 100 } },
+          actual = _.keyBy(array, 'dir');
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should only add values to own, not inherited, properties', function(assert) {
+      assert.expect(2);
+
+      var actual = _.keyBy([6.1, 4.2, 6.3], function(n) {
+        return Math.floor(n) > 4 ? 'hasOwnProperty' : 'constructor';
+      });
+
+      assert.deepEqual(actual.constructor, 4.2);
+      assert.deepEqual(actual.hasOwnProperty, 6.3);
+    });
+
+    QUnit.test('should work with a number for `iteratee`', function(assert) {
+      assert.expect(2);
+
+      var array = [
+        [1, 'a'],
+        [2, 'a'],
+        [2, 'b']
+      ];
+
+      assert.deepEqual(_.keyBy(array, 0), { '1': [1, 'a'], '2': [2, 'b'] });
+      assert.deepEqual(_.keyBy(array, 1), { 'a': [2, 'a'], 'b': [2, 'b'] });
+    });
+
+    QUnit.test('should work with an object for `collection`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.keyBy({ 'a': 6.1, 'b': 4.2, 'c': 6.3 }, Math.floor);
+      assert.deepEqual(actual, { '4': 4.2, '6': 6.3 });
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE).concat(
+          lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 2), LARGE_ARRAY_SIZE),
+          lodashStable.range(Math.floor(LARGE_ARRAY_SIZE / 1.5), LARGE_ARRAY_SIZE)
+        );
+
+        var actual = _(array).keyBy().map(square).filter(isEven).take().value();
+
+        assert.deepEqual(actual, _.take(_.filter(_.map(_.keyBy(array), square), isEven)));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('keys methods');
+
+  lodashStable.each(['keys', 'keysIn'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        strictArgs = (function() { 'use strict'; return arguments; }(1, 2, 3)),
+        func = _[methodName],
+        isKeys = methodName == 'keys';
+
+    QUnit.test('`_.' + methodName + '` should return the string keyed property names of `object`', function(assert) {
+      assert.expect(1);
+
+      var actual = func({ 'a': 1, 'b': 1 }).sort();
+
+      assert.deepEqual(actual, ['a', 'b']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var expected = isKeys ? ['a'] : ['a', 'b'],
+          actual = func(new Foo).sort();
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce primitives to objects (test in IE 9)', function(assert) {
+      assert.expect(2);
+
+      var expected = lodashStable.map(primitives, function(value) {
+        return typeof value == 'string' ? ['0'] : [];
+      });
+
+      var actual = lodashStable.map(primitives, func);
+      assert.deepEqual(actual, expected);
+
+      // IE 9 doesn't box numbers in for-in loops.
+      numberProto.a = 1;
+      assert.deepEqual(func(0), isKeys ? [] : ['a']);
+      delete numberProto.a;
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat sparse arrays as dense', function(assert) {
+      assert.expect(1);
+
+      var array = [1];
+      array[2] = 3;
+
+      var actual = func(array).sort();
+
+      assert.deepEqual(actual, ['0', '1', '2']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not coerce nullish values to objects', function(assert) {
+      assert.expect(2);
+
+      objectProto.a = 1;
+      lodashStable.each([null, undefined], function(value) {
+        assert.deepEqual(func(value), []);
+      });
+      delete objectProto.a;
+    });
+
+    QUnit.test('`_.' + methodName + '` should return keys for custom properties on arrays', function(assert) {
+      assert.expect(1);
+
+      var array = [1];
+      array.a = 1;
+
+      var actual = func(array).sort();
+
+      assert.deepEqual(actual, ['0', 'a']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties of arrays', function(assert) {
+      assert.expect(1);
+
+      arrayProto.a = 1;
+
+      var expected = isKeys ? ['0'] : ['0', 'a'],
+          actual = func([1]).sort();
+
+      assert.deepEqual(actual, expected);
+
+      delete arrayProto.a;
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      var values = [args, strictArgs],
+          expected = lodashStable.map(values, lodashStable.constant(['0', '1', '2']));
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(value).sort();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return keys for custom properties on `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      var values = [args, strictArgs],
+          expected = lodashStable.map(values, lodashStable.constant(['0', '1', '2', 'a']));
+
+      var actual = lodashStable.map(values, function(value) {
+        value.a = 1;
+        var result = func(value).sort();
+        delete value.a;
+        return result;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties of `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      var values = [args, strictArgs],
+          expected = lodashStable.map(values, lodashStable.constant(isKeys ? ['0', '1', '2'] : ['0', '1', '2', 'a']));
+
+      var actual = lodashStable.map(values, function(value) {
+        objectProto.a = 1;
+        var result = func(value).sort();
+        delete objectProto.a;
+        return result;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with string objects', function(assert) {
+      assert.expect(1);
+
+      var actual = func(Object('abc')).sort();
+
+      assert.deepEqual(actual, ['0', '1', '2']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return keys for custom properties on string objects', function(assert) {
+      assert.expect(1);
+
+      var object = Object('a');
+      object.a = 1;
+
+      var actual = func(object).sort();
+
+      assert.deepEqual(actual, ['0', 'a']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isKeys ? 'not ' : '') + 'include inherited string keyed properties of string objects', function(assert) {
+      assert.expect(1);
+
+      stringProto.a = 1;
+
+      var expected = isKeys ? ['0'] : ['0', 'a'],
+          actual = func(Object('a')).sort();
+
+      assert.deepEqual(actual, expected);
+
+      delete stringProto.a;
+    });
+
+    QUnit.test('`_.' + methodName + '` skips the `constructor` property on prototype objects', function(assert) {
+      assert.expect(3);
+
+      function Foo() {}
+      Foo.prototype.a = 1;
+
+      var expected = ['a'];
+      assert.deepEqual(func(Foo.prototype), expected);
+
+      Foo.prototype = { 'constructor': Foo, 'a': 1 };
+      assert.deepEqual(func(Foo.prototype), expected);
+
+      var Fake = { 'prototype': {} };
+      Fake.prototype.constructor = Fake;
+      assert.deepEqual(func(Fake.prototype), ['constructor']);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.last');
+
+  (function() {
+    var array = [1, 2, 3, 4];
+
+    QUnit.test('should return the last element', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.last(array), 4);
+    });
+
+    QUnit.test('should return `undefined` when querying empty arrays', function(assert) {
+      assert.expect(1);
+
+      var array = [];
+      array['-1'] = 1;
+
+      assert.strictEqual(_.last([]), undefined);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.last);
+
+      assert.deepEqual(actual, [3, 6, 9]);
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_(array).last(), 4);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(array).chain().last() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should not execute immediately when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _(array).chain().last();
+        assert.strictEqual(wrapped.__wrapped__, array);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var largeArray = lodashStable.range(LARGE_ARRAY_SIZE),
+            smallArray = array;
+
+        lodashStable.times(2, function(index) {
+          var array = index ? largeArray : smallArray,
+              wrapped = _(array).filter(isEven);
+
+          assert.strictEqual(wrapped.last(), _.last(_.filter(array, isEven)));
+        });
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.lowerCase');
+
+  (function() {
+    QUnit.test('should lowercase as space-separated words', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.lowerCase('--Foo-Bar--'), 'foo bar');
+      assert.strictEqual(_.lowerCase('fooBar'), 'foo bar');
+      assert.strictEqual(_.lowerCase('__FOO_BAR__'), 'foo bar');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.lowerFirst');
+
+  (function() {
+    QUnit.test('should lowercase only the first character', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.lowerFirst('fred'), 'fred');
+      assert.strictEqual(_.lowerFirst('Fred'), 'fred');
+      assert.strictEqual(_.lowerFirst('FRED'), 'fRED');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.lt');
+
+  (function() {
+    QUnit.test('should return `true` if `value` is less than `other`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.lt(1, 3), true);
+      assert.strictEqual(_.lt('abc', 'def'), true);
+    });
+
+    QUnit.test('should return `false` if `value` >= `other`', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.lt(3, 1), false);
+      assert.strictEqual(_.lt(3, 3), false);
+      assert.strictEqual(_.lt('def', 'abc'), false);
+      assert.strictEqual(_.lt('def', 'def'), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.lte');
+
+  (function() {
+    QUnit.test('should return `true` if `value` is <= `other`', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.lte(1, 3), true);
+      assert.strictEqual(_.lte(3, 3), true);
+      assert.strictEqual(_.lte('abc', 'def'), true);
+      assert.strictEqual(_.lte('def', 'def'), true);
+    });
+
+    QUnit.test('should return `false` if `value` > `other`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.lt(3, 1), false);
+      assert.strictEqual(_.lt('def', 'abc'), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.lastIndexOf');
+
+  (function() {
+    var array = [1, 2, 3, 1, 2, 3];
+
+    QUnit.test('should return the index of the last matched value', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.lastIndexOf(array, 3), 5);
+    });
+
+    QUnit.test('should work with a positive `fromIndex`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.lastIndexOf(array, 1, 2), 0);
+    });
+
+    QUnit.test('should work with `fromIndex` >= `array.length`', function(assert) {
+      assert.expect(1);
+
+      var values = [6, 8, Math.pow(2, 32), Infinity],
+          expected = lodashStable.map(values, lodashStable.constant([-1, 3, -1]));
+
+      var actual = lodashStable.map(values, function(fromIndex) {
+        return [
+          _.lastIndexOf(array, undefined, fromIndex),
+          _.lastIndexOf(array, 1, fromIndex),
+          _.lastIndexOf(array, '', fromIndex)
+        ];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with a negative `fromIndex`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.lastIndexOf(array, 2, -3), 1);
+    });
+
+    QUnit.test('should work with a negative `fromIndex` <= `-array.length`', function(assert) {
+      assert.expect(1);
+
+      var values = [-6, -8, -Infinity],
+          expected = lodashStable.map(values, alwaysZero);
+
+      var actual = lodashStable.map(values, function(fromIndex) {
+        return _.lastIndexOf(array, 1, fromIndex);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat falsey `fromIndex` values correctly', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? 5 : -1;
+      });
+
+      var actual = lodashStable.map(falsey, function(fromIndex) {
+        return _.lastIndexOf(array, 3, fromIndex);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce `fromIndex` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.lastIndexOf(array, 2, 4.2), 4);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('indexOf methods');
+
+  lodashStable.each(['indexOf', 'lastIndexOf', 'sortedIndexOf', 'sortedLastIndexOf'], function(methodName) {
+    var func = _[methodName],
+        isIndexOf = !/last/i.test(methodName),
+        isSorted = /^sorted/.test(methodName);
+
+    QUnit.test('`_.' + methodName + '` should accept a falsey `array` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, lodashStable.constant(-1));
+
+      var actual = lodashStable.map(falsey, function(array, index) {
+        try {
+          return index ? func(array) : func();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `-1` for an unmatched value', function(assert) {
+      assert.expect(5);
+
+      var array = [1, 2, 3],
+          empty = [];
+
+      assert.strictEqual(func(array, 4), -1);
+      assert.strictEqual(func(array, 4, true), -1);
+      assert.strictEqual(func(array, undefined, true), -1);
+
+      assert.strictEqual(func(empty, undefined), -1);
+      assert.strictEqual(func(empty, undefined, true), -1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not match values on empty arrays', function(assert) {
+      assert.expect(2);
+
+      var array = [];
+      array[-1] = 0;
+
+      assert.strictEqual(func(array, undefined), -1);
+      assert.strictEqual(func(array, 0, true), -1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should match `NaN`', function(assert) {
+      assert.expect(3);
+
+      var array = isSorted
+        ? [1, 2, NaN, NaN]
+        : [1, NaN, 3, NaN, 5, NaN];
+
+      if (isSorted) {
+        assert.strictEqual(func(array, NaN, true), isIndexOf ? 2 : 3);
+        skipAssert(assert, 2);
+      }
+      else {
+        assert.strictEqual(func(array, NaN), isIndexOf ? 1 : 5);
+        assert.strictEqual(func(array, NaN, 2), isIndexOf ? 3 : 1);
+        assert.strictEqual(func(array, NaN, -2), isIndexOf ? 5 : 3);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should match `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(func([-0], 0), 0);
+      assert.strictEqual(func([0], -0), 0);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.map');
+
+  (function() {
+    var array = [1, 2];
+
+    QUnit.test('should map values in `collection` to a new array', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1, 'b': 2 },
+          expected = ['1', '2'];
+
+      assert.deepEqual(_.map(array, String), expected);
+      assert.deepEqual(_.map(object, String), expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': 'x' }, { 'a': 'y' }];
+      assert.deepEqual(_.map(objects, 'a'), ['x', 'y']);
+    });
+
+    QUnit.test('should iterate over own string keyed properties of objects', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var actual = _.map(new Foo, identity);
+      assert.deepEqual(actual, [1]);
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1, 'b': 2 },
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([1, 2]));
+
+      lodashStable.each([array, object], function(collection) {
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? _.map(collection, value) : _.map(collection);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should accept a falsey `collection` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyArray);
+
+      var actual = lodashStable.map(falsey, function(collection, index) {
+        try {
+          return index ? _.map(collection) : _.map();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat number values for `collection` as empty', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.map(1), []);
+    });
+
+    QUnit.test('should treat a nodelist as an array-like object', function(assert) {
+      assert.expect(1);
+
+      if (document) {
+        var actual = _.map(document.getElementsByTagName('body'), function(element) {
+          return element.nodeName.toLowerCase();
+        });
+
+        assert.deepEqual(actual, ['body']);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work with objects with non-number length properties', function(assert) {
+      assert.expect(1);
+
+      var value = { 'value': 'x' },
+          object = { 'length': { 'value': 'x' } };
+
+      assert.deepEqual(_.map(object, identity), [value]);
+    });
+
+    QUnit.test('should return a wrapped value when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(array).map(noop) instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments in a lazy sequence', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var args,
+            array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            expected = [1, 0, _.map(array.slice(1), square)];
+
+        _(array).slice(1).map(function(value, index, array) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, [1, 0, array.slice(1)]);
+
+        args = undefined;
+        _(array).slice(1).map(square).map(function(value, index, array) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        args = undefined;
+        _(array).slice(1).map(square).map(function(value, index) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        args = undefined;
+        _(array).slice(1).map(square).map(function(value) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, [1]);
+
+        args = undefined;
+        _(array).slice(1).map(square).map(function() {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, expected);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.mapKeys');
+
+  (function() {
+    var array = [1, 2],
+        object = { 'a': 1, 'b': 2 };
+
+    QUnit.test('should map keys in `object` to a new object', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mapKeys(object, String);
+      assert.deepEqual(actual, { '1': 1, '2': 2 });
+    });
+
+    QUnit.test('should treat arrays like objects', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mapKeys(array, String);
+      assert.deepEqual(actual, { '1': 1, '2': 2 });
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mapKeys({ 'a': { 'b': 'c' } }, 'b');
+      assert.deepEqual(actual, { 'c': { 'b': 'c' } });
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2 },
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant({ '1': 1, '2': 2 }));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.mapKeys(object, value) : _.mapKeys(object);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.mapValues');
+
+  (function() {
+    var array = [1, 2],
+        object = { 'a': 1, 'b': 2 };
+
+    QUnit.test('should map values in `object` to a new object', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mapValues(object, String);
+      assert.deepEqual(actual, { 'a': '1', 'b': '2' });
+    });
+
+    QUnit.test('should treat arrays like objects', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mapValues(array, String);
+      assert.deepEqual(actual, { '0': '1', '1': '2' });
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mapValues({ 'a': { 'b': 1 } }, 'b');
+      assert.deepEqual(actual, { 'a': 1 });
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2 },
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([true, false]));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var result = index ? _.mapValues(object, value) : _.mapValues(object);
+        return [lodashStable.isEqual(result, object), result === object];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.mapKeys and lodash.mapValues');
+
+  lodashStable.each(['mapKeys', 'mapValues'], function(methodName) {
+    var func = _[methodName],
+        object = { 'a': 1, 'b': 2 };
+
+    QUnit.test('`_.' + methodName + '` should iterate over own string keyed properties of objects', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 'a';
+      }
+      Foo.prototype.b = 'b';
+
+      var actual = func(new Foo, function(value, key) { return key; });
+      assert.deepEqual(actual, { 'a': 'a' });
+    });
+
+    QUnit.test('`_.' + methodName + '` should accept a falsey `object` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyObject);
+
+      var actual = lodashStable.map(falsey, function(object, index) {
+        try {
+          return index ? func(object) : func();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(object)[methodName](noop) instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.matches');
+
+  (function() {
+    QUnit.test('should create a function that performs a deep comparison between `source` and a given object', function(assert) {
+      assert.expect(6);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          matches = _.matches({ 'a': 1 });
+
+      assert.strictEqual(matches.length, 1);
+      assert.strictEqual(matches(object), true);
+
+      matches = _.matches({ 'b': 1 });
+      assert.strictEqual(matches(object), false);
+
+      matches = _.matches({ 'a': 1, 'c': 3 });
+      assert.strictEqual(matches(object), true);
+
+      matches = _.matches({ 'c': 3, 'd': 4 });
+      assert.strictEqual(matches(object), false);
+
+      object = { 'a': { 'b': { 'c': 1, 'd': 2 }, 'e': 3 }, 'f': 4 };
+      matches = _.matches({ 'a': { 'b': { 'c': 1 } } });
+
+      assert.strictEqual(matches(object), true);
+    });
+
+    QUnit.test('should match inherited string keyed `object` properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var object = { 'a': new Foo },
+          matches = _.matches({ 'a': { 'b': 2 } });
+
+      assert.strictEqual(matches(object), true);
+    });
+
+    QUnit.test('should not match by inherited `source` properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': 2 }],
+          source = new Foo,
+          actual = lodashStable.map(objects, _.matches(source)),
+          expected = lodashStable.map(objects, alwaysTrue);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should compare a variety of `source` property values', function(assert) {
+      assert.expect(2);
+
+      var object1 = { 'a': false, 'b': true, 'c': '3', 'd': 4, 'e': [5], 'f': { 'g': 6 } },
+          object2 = { 'a': 0, 'b': 1, 'c': 3, 'd': '4', 'e': ['5'], 'f': { 'g': '6' } },
+          matches = _.matches(object1);
+
+      assert.strictEqual(matches(object1), true);
+      assert.strictEqual(matches(object2), false);
+    });
+
+    QUnit.test('should match `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      var object1 = { 'a': -0 },
+          object2 = { 'a': 0 },
+          matches = _.matches(object1);
+
+      assert.strictEqual(matches(object2), true);
+
+      matches = _.matches(object2);
+      assert.strictEqual(matches(object1), true);
+    });
+
+    QUnit.test('should compare functions by reference', function(assert) {
+      assert.expect(3);
+
+      var object1 = { 'a': lodashStable.noop },
+          object2 = { 'a': noop },
+          object3 = { 'a': {} },
+          matches = _.matches(object1);
+
+      assert.strictEqual(matches(object1), true);
+      assert.strictEqual(matches(object2), false);
+      assert.strictEqual(matches(object3), false);
+    });
+
+    QUnit.test('should work with a function for `object`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.a = { 'b': 1, 'c': 2 };
+
+      var matches = _.matches({ 'a': { 'b': 1 } });
+      assert.strictEqual(matches(Foo), true);
+    });
+
+    QUnit.test('should work with a function for `source`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.a = 1;
+      Foo.b = function() {};
+      Foo.c = 3;
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': Foo.b, 'c': 3 }],
+          actual = lodashStable.map(objects, _.matches(Foo));
+
+      assert.deepEqual(actual, [false, true]);
+    });
+
+    QUnit.test('should work with a non-plain `object`', function(assert) {
+      assert.expect(1);
+
+      function Foo(object) { lodashStable.assign(this, object); }
+
+      var object = new Foo({ 'a': new Foo({ 'b': 1, 'c': 2 }) }),
+          matches = _.matches({ 'a': { 'b': 1 } });
+
+      assert.strictEqual(matches(object), true);
+    });
+
+    QUnit.test('should partial match arrays', function(assert) {
+      assert.expect(3);
+
+      var objects = [{ 'a': ['b'] }, { 'a': ['c', 'd'] }],
+          actual = lodashStable.filter(objects, _.matches({ 'a': ['d'] }));
+
+      assert.deepEqual(actual, [objects[1]]);
+
+      actual = lodashStable.filter(objects, _.matches({ 'a': ['b', 'd'] }));
+      assert.deepEqual(actual, []);
+
+      actual = lodashStable.filter(objects, _.matches({ 'a': ['d', 'b'] }));
+      assert.deepEqual(actual, []);
+    });
+
+    QUnit.test('should partial match arrays of objects', function(assert) {
+      assert.expect(1);
+
+      var objects = [
+        { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 5, 'd': 6 }] },
+        { 'a': [{ 'b': 1, 'c': 2 }, { 'b': 4, 'c': 6, 'd': 7 }] }
+      ];
+
+      var actual = lodashStable.filter(objects, _.matches({ 'a': [{ 'b': 1 }, { 'b': 4, 'c': 5 }] }));
+      assert.deepEqual(actual, [objects[0]]);
+    });
+
+    QUnit.test('should partial match maps', function(assert) {
+      assert.expect(3);
+
+      if (Map) {
+        var objects = [{ 'a': new Map }, { 'a': new Map }];
+        objects[0].a.set('a', 1);
+        objects[1].a.set('a', 1);
+        objects[1].a.set('b', 2);
+
+        var map = new Map;
+        map.set('b', 2);
+        var actual = lodashStable.filter(objects, _.matches({ 'a': map }));
+
+        assert.deepEqual(actual, [objects[1]]);
+
+        map['delete']('b');
+        actual = lodashStable.filter(objects, _.matches({ 'a': map }));
+
+        assert.deepEqual(actual, objects);
+
+        map.set('c', 3);
+        actual = lodashStable.filter(objects, _.matches({ 'a': map }));
+
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should partial match sets', function(assert) {
+      assert.expect(3);
+
+      if (Set) {
+        var objects = [{ 'a': new Set }, { 'a': new Set }];
+        objects[0].a.add(1);
+        objects[1].a.add(1);
+        objects[1].a.add(2);
+
+        var set = new Set;
+        set.add(2);
+        var actual = lodashStable.filter(objects, _.matches({ 'a': set }));
+
+        assert.deepEqual(actual, [objects[1]]);
+
+        set['delete'](2);
+        actual = lodashStable.filter(objects, _.matches({ 'a': set }));
+
+        assert.deepEqual(actual, objects);
+
+        set.add(3);
+        actual = lodashStable.filter(objects, _.matches({ 'a': set }));
+
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should match `undefined` values', function(assert) {
+      assert.expect(3);
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }],
+          actual = lodashStable.map(objects, _.matches({ 'b': undefined })),
+          expected = [false, false, true];
+
+      assert.deepEqual(actual, expected);
+
+      actual = lodashStable.map(objects, _.matches({ 'a': 1, 'b': undefined }));
+
+      assert.deepEqual(actual, expected);
+
+      objects = [{ 'a': { 'b': 1 } }, { 'a': { 'b': 1, 'c': 1 } }, { 'a': { 'b': 1, 'c': undefined } }];
+      actual = lodashStable.map(objects, _.matches({ 'a': { 'c': undefined } }));
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should match `undefined` values on primitives', function(assert) {
+      assert.expect(3);
+
+      numberProto.a = 1;
+      numberProto.b = undefined;
+
+      try {
+        var matches = _.matches({ 'b': undefined });
+        assert.strictEqual(matches(1), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      try {
+        matches = _.matches({ 'a': 1, 'b': undefined });
+        assert.strictEqual(matches(1), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      numberProto.a = { 'b': 1, 'c': undefined };
+      try {
+        matches = _.matches({ 'a': { 'c': undefined } });
+        assert.strictEqual(matches(1), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      delete numberProto.a;
+      delete numberProto.b;
+    });
+
+    QUnit.test('should return `false` when `object` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse),
+          matches = _.matches({ 'a': 1 });
+
+      var actual = lodashStable.map(values, function(value, index) {
+        try {
+          return index ? matches(value) : matches();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing an empty `source` to a nullish `object`', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysTrue),
+          matches = _.matches({});
+
+      var actual = lodashStable.map(values, function(value, index) {
+        try {
+          return index ? matches(value) : matches();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing an empty `source`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1 },
+          expected = lodashStable.map(empties, alwaysTrue);
+
+      var actual = lodashStable.map(empties, function(value) {
+        var matches = _.matches(value);
+        return matches(object);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` when comparing a `source` of empty arrays and objects', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': [1], 'b': { 'c': 1 } }, { 'a': [2, 3], 'b': { 'd': 2 } }],
+          actual = lodashStable.filter(objects, _.matches({ 'a': [], 'b': {} }));
+
+      assert.deepEqual(actual, objects);
+    });
+
+    QUnit.test('should not change behavior if `source` is modified', function(assert) {
+      assert.expect(9);
+
+      var sources = [
+        { 'a': { 'b': 2, 'c': 3 } },
+        { 'a': 1, 'b': 2 },
+        { 'a': 1 }
+      ];
+
+      lodashStable.each(sources, function(source, index) {
+        var object = lodashStable.cloneDeep(source),
+            matches = _.matches(source);
+
+        assert.strictEqual(matches(object), true);
+
+        if (index) {
+          source.a = 2;
+          source.b = 1;
+          source.c = 3;
+        } else {
+          source.a.b = 1;
+          source.a.c = 2;
+          source.a.d = 3;
+        }
+        assert.strictEqual(matches(object), true);
+        assert.strictEqual(matches(source), false);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.matchesProperty');
+
+  (function() {
+    QUnit.test('should create a function that performs a deep comparison between a property value and `srcValue`', function(assert) {
+      assert.expect(6);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          matches = _.matchesProperty('a', 1);
+
+      assert.strictEqual(matches.length, 1);
+      assert.strictEqual(matches(object), true);
+
+      matches = _.matchesProperty('b', 3);
+      assert.strictEqual(matches(object), false);
+
+      matches = _.matchesProperty('a', { 'a': 1, 'c': 3 });
+      assert.strictEqual(matches({ 'a': object }), true);
+
+      matches = _.matchesProperty('a', { 'c': 3, 'd': 4 });
+      assert.strictEqual(matches(object), false);
+
+      object = { 'a': { 'b': { 'c': 1, 'd': 2 }, 'e': 3 }, 'f': 4 };
+      matches = _.matchesProperty('a', { 'b': { 'c': 1 } });
+
+      assert.strictEqual(matches(object), true);
+    });
+
+    QUnit.test('should support deep paths', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var matches = _.matchesProperty(path, 2);
+        assert.strictEqual(matches(object), true);
+      });
+    });
+
+    QUnit.test('should work with a non-string `path`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3];
+
+      lodashStable.each([1, [1]], function(path) {
+        var matches = _.matchesProperty(path, 2);
+        assert.strictEqual(matches(array), true);
+      });
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object1 = { '-0': 'a' },
+          object2 = { '0': 'b' },
+          pairs = [[object1, object2], [object1, object2], [object2, object1], [object2, object1]],
+          props = [-0, Object(-0), 0, Object(0)],
+          values = ['a', 'a', 'b', 'b'],
+          expected = lodashStable.map(props, lodashStable.constant([true, false]));
+
+      var actual = lodashStable.map(props, function(key, index) {
+        var matches = _.matchesProperty(key, values[index]),
+            pair = pairs[index];
+
+        return [matches(pair[0]), matches(pair[1])];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce key to a string', function(assert) {
+      assert.expect(1);
+
+      function fn() {}
+      fn.toString = lodashStable.constant('fn');
+
+      var objects = [{ 'null': 1 }, { 'undefined': 2 }, { 'fn': 3 }, { '[object Object]': 4 }],
+          values = [null, undefined, fn, {}];
+
+      var expected = lodashStable.transform(values, function(result) {
+        result.push(true, true);
+      });
+
+      var actual = lodashStable.transform(objects, function(result, object, index) {
+        var key = values[index];
+        lodashStable.each([key, [key]], function(path) {
+          var matches = _.matchesProperty(path, object[key]);
+          result.push(matches(object));
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should match a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': 1, 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        var matches = _.matchesProperty(path, 1);
+        assert.strictEqual(matches(object), true);
+      });
+    });
+
+    QUnit.test('should return `false` if parts of `path` are missing', function(assert) {
+      assert.expect(4);
+
+      var object = {};
+
+      lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) {
+        var matches = _.matchesProperty(path, 1);
+        assert.strictEqual(matches(object), false);
+      });
+    });
+
+    QUnit.test('should return `false` for deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) {
+        var matches = _.matchesProperty(path, 1);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          try {
+            return index ? matches(value) : matches();
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should match inherited string keyed `srcValue` properties', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.b = 2;
+
+      var object = { 'a': new Foo };
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var matches = _.matchesProperty(path, { 'b': 2 });
+        assert.strictEqual(matches(object), true);
+      });
+    });
+
+    QUnit.test('should not match by inherited `srcValue` properties', function(assert) {
+      assert.expect(2);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': 2 } }],
+          expected = lodashStable.map(objects, alwaysTrue);
+
+      lodashStable.each(['a', ['a']], function(path) {
+        assert.deepEqual(lodashStable.map(objects, _.matchesProperty(path, new Foo)), expected);
+      });
+    });
+
+    QUnit.test('should compare a variety of values', function(assert) {
+      assert.expect(2);
+
+      var object1 = { 'a': false, 'b': true, 'c': '3', 'd': 4, 'e': [5], 'f': { 'g': 6 } },
+          object2 = { 'a': 0, 'b': 1, 'c': 3, 'd': '4', 'e': ['5'], 'f': { 'g': '6' } },
+          matches = _.matchesProperty('a', object1);
+
+      assert.strictEqual(matches({ 'a': object1 }), true);
+      assert.strictEqual(matches({ 'a': object2 }), false);
+    });
+
+    QUnit.test('should match `-0` as `0`', function(assert) {
+      assert.expect(2);
+
+      var matches = _.matchesProperty('a', -0);
+      assert.strictEqual(matches({ 'a': 0 }), true);
+
+      matches = _.matchesProperty('a', 0);
+      assert.strictEqual(matches({ 'a': -0 }), true);
+    });
+
+    QUnit.test('should compare functions by reference', function(assert) {
+      assert.expect(3);
+
+      var object1 = { 'a': lodashStable.noop },
+          object2 = { 'a': noop },
+          object3 = { 'a': {} },
+          matches = _.matchesProperty('a', object1);
+
+      assert.strictEqual(matches({ 'a': object1 }), true);
+      assert.strictEqual(matches({ 'a': object2 }), false);
+      assert.strictEqual(matches({ 'a': object3 }), false);
+    });
+
+    QUnit.test('should work with a function for `srcValue`', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.a = 1;
+      Foo.b = function() {};
+      Foo.c = 3;
+
+      var objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': Foo.b, 'c': 3 } }],
+          actual = lodashStable.map(objects, _.matchesProperty('a', Foo));
+
+      assert.deepEqual(actual, [false, true]);
+    });
+
+    QUnit.test('should work with a non-plain `srcValue`', function(assert) {
+      assert.expect(1);
+
+      function Foo(object) { lodashStable.assign(this, object); }
+
+      var object = new Foo({ 'a': new Foo({ 'b': 1, 'c': 2 }) }),
+          matches = _.matchesProperty('a', { 'b': 1 });
+
+      assert.strictEqual(matches(object), true);
+    });
+
+    QUnit.test('should partial match arrays', function(assert) {
+      assert.expect(3);
+
+      var objects = [{ 'a': ['b'] }, { 'a': ['c', 'd'] }],
+          actual = lodashStable.filter(objects, _.matchesProperty('a', ['d']));
+
+      assert.deepEqual(actual, [objects[1]]);
+
+      actual = lodashStable.filter(objects, _.matchesProperty('a', ['b', 'd']));
+      assert.deepEqual(actual, []);
+
+      actual = lodashStable.filter(objects, _.matchesProperty('a', ['d', 'b']));
+      assert.deepEqual(actual, []);
+    });
+
+    QUnit.test('should partial match arrays of objects', function(assert) {
+      assert.expect(1);
+
+      var objects = [
+        { 'a': [{ 'a': 1, 'b': 2 }, { 'a': 4, 'b': 5, 'c': 6 }] },
+        { 'a': [{ 'a': 1, 'b': 2 }, { 'a': 4, 'b': 6, 'c': 7 }] }
+      ];
+
+      var actual = lodashStable.filter(objects, _.matchesProperty('a', [{ 'a': 1 }, { 'a': 4, 'b': 5 }]));
+      assert.deepEqual(actual, [objects[0]]);
+    });
+    QUnit.test('should partial match maps', function(assert) {
+      assert.expect(3);
+
+      if (Map) {
+        var objects = [{ 'a': new Map }, { 'a': new Map }];
+        objects[0].a.set('a', 1);
+        objects[1].a.set('a', 1);
+        objects[1].a.set('b', 2);
+
+        var map = new Map;
+        map.set('b', 2);
+        var actual = lodashStable.filter(objects, _.matchesProperty('a', map));
+
+        assert.deepEqual(actual, [objects[1]]);
+
+        map['delete']('b');
+        actual = lodashStable.filter(objects, _.matchesProperty('a', map));
+
+        assert.deepEqual(actual, objects);
+
+        map.set('c', 3);
+        actual = lodashStable.filter(objects, _.matchesProperty('a', map));
+
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should partial match sets', function(assert) {
+      assert.expect(3);
+
+      if (Set) {
+        var objects = [{ 'a': new Set }, { 'a': new Set }];
+        objects[0].a.add(1);
+        objects[1].a.add(1);
+        objects[1].a.add(2);
+
+        var set = new Set;
+        set.add(2);
+        var actual = lodashStable.filter(objects, _.matchesProperty('a', set));
+
+        assert.deepEqual(actual, [objects[1]]);
+
+        set['delete'](2);
+        actual = lodashStable.filter(objects, _.matchesProperty('a', set));
+
+        assert.deepEqual(actual, objects);
+
+        set.add(3);
+        actual = lodashStable.filter(objects, _.matchesProperty('a', set));
+
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should match `undefined` values', function(assert) {
+      assert.expect(2);
+
+      var objects = [{ 'a': 1 }, { 'a': 1, 'b': 1 }, { 'a': 1, 'b': undefined }],
+          actual = lodashStable.map(objects, _.matchesProperty('b', undefined)),
+          expected = [false, false, true];
+
+      assert.deepEqual(actual, expected);
+
+      objects = [{ 'a': { 'a': 1 } }, { 'a': { 'a': 1, 'b': 1 } }, { 'a': { 'a': 1, 'b': undefined } }];
+      actual = lodashStable.map(objects, _.matchesProperty('a', { 'b': undefined }));
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should match `undefined` values of nested objects', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': { 'b': undefined } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var matches = _.matchesProperty(path, undefined);
+        assert.strictEqual(matches(object), true);
+      });
+
+      lodashStable.each(['a.a', ['a', 'a']], function(path) {
+        var matches = _.matchesProperty(path, undefined);
+        assert.strictEqual(matches(object), false);
+      });
+    });
+
+    QUnit.test('should match `undefined` values on primitives', function(assert) {
+      assert.expect(2);
+
+      numberProto.a = 1;
+      numberProto.b = undefined;
+
+      try {
+        var matches = _.matchesProperty('b', undefined);
+        assert.strictEqual(matches(1), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      numberProto.a = { 'b': 1, 'c': undefined };
+      try {
+        matches = _.matchesProperty('a', { 'c': undefined });
+        assert.strictEqual(matches(1), true);
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+      delete numberProto.a;
+      delete numberProto.b;
+    });
+
+    QUnit.test('should return `false` when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        var matches = _.matchesProperty(path, 1);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          try {
+            return index ? matches(value) : matches();
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `true` when comparing a `srcValue` of empty arrays and objects', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': [1], 'b': { 'c': 1 } }, { 'a': [2, 3], 'b': { 'd': 2 } }],
+          matches = _.matchesProperty('a', { 'a': [], 'b': {} });
+
+      var actual = lodashStable.filter(objects, function(object) {
+        return matches({ 'a': object });
+      });
+
+      assert.deepEqual(actual, objects);
+    });
+
+    QUnit.test('should not change behavior if `srcValue` is modified', function(assert) {
+      assert.expect(9);
+
+      lodashStable.each([{ 'a': { 'b': 2, 'c': 3 } }, { 'a': 1, 'b': 2 }, { 'a': 1 }], function(source, index) {
+        var object = lodashStable.cloneDeep(source),
+            matches = _.matchesProperty('a', source);
+
+        assert.strictEqual(matches({ 'a': object }), true);
+
+        if (index) {
+          source.a = 2;
+          source.b = 1;
+          source.c = 3;
+        } else {
+          source.a.b = 1;
+          source.a.c = 2;
+          source.a.d = 3;
+        }
+        assert.strictEqual(matches({ 'a': object }), true);
+        assert.strictEqual(matches({ 'a': source }), false);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.max');
+
+  (function() {
+    QUnit.test('should return the largest value from a collection', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.max([1, 2, 3]), 3);
+    });
+
+    QUnit.test('should return `undefined` for empty collections', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat([[]]),
+          expected = lodashStable.map(values, noop);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        try {
+          return index ? _.max(value) : _.max();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with non-numeric collection values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.max(['a', 'b']), 'b');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.mean');
+
+  (function() {
+    QUnit.test('should return the mean of an array of numbers', function(assert) {
+      assert.expect(1);
+
+      var array = [4, 2, 8, 6];
+      assert.strictEqual(_.mean(array), 5);
+    });
+
+    QUnit.test('should return `NaN` when passing empty `array` values', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, alwaysNaN),
+          actual = lodashStable.map(empties, _.mean);
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.meanBy');
+
+  (function() {
+    var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }];
+
+    QUnit.test('should work with an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var actual = _.meanBy(objects, function(object) {
+        return object.a;
+      });
+
+      assert.deepEqual(actual, 2);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.meanBy(objects, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [{ 'a': 2 }]);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var arrays = [[2], [3], [1]];
+      assert.strictEqual(_.meanBy(arrays, 0), 2);
+      assert.strictEqual(_.meanBy(objects, 'a'), 2);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.memoize');
+
+  (function() {
+    QUnit.test('should memoize results based on the first argument given', function(assert) {
+      assert.expect(2);
+
+      var memoized = _.memoize(function(a, b, c) {
+        return a + b + c;
+      });
+
+      assert.strictEqual(memoized(1, 2, 3), 6);
+      assert.strictEqual(memoized(1, 3, 5), 6);
+    });
+
+    QUnit.test('should support a `resolver` argument', function(assert) {
+      assert.expect(2);
+
+      var fn = function(a, b, c) { return a + b + c; },
+          memoized = _.memoize(fn, fn);
+
+      assert.strictEqual(memoized(1, 2, 3), 6);
+      assert.strictEqual(memoized(1, 3, 5), 9);
+    });
+
+    QUnit.test('should use `this` binding of function for `resolver`', function(assert) {
+      assert.expect(2);
+
+      var fn = function(a, b, c) { return a + this.b + this.c; },
+          memoized = _.memoize(fn, fn);
+
+      var object = { 'memoized': memoized, 'b': 2, 'c': 3 };
+      assert.strictEqual(object.memoized(1), 6);
+
+      object.b = 3;
+      object.c = 5;
+      assert.strictEqual(object.memoized(1), 9);
+    });
+
+    QUnit.test('should throw a TypeError if `resolve` is truthy and not a function', function(assert) {
+      assert.expect(1);
+
+      assert.raises(function() { _.memoize(noop, true); }, TypeError);
+    });
+
+    QUnit.test('should not error if `resolver` is falsey', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysTrue);
+
+      var actual = lodashStable.map(falsey, function(resolver, index) {
+        try {
+          return _.isFunction(index ? _.memoize(noop, resolver) : _.memoize(noop));
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should check cache for own properties', function(assert) {
+      assert.expect(1);
+
+      var props = [
+        'constructor',
+        'hasOwnProperty',
+        'isPrototypeOf',
+        'propertyIsEnumerable',
+        'toLocaleString',
+        'toString',
+        'valueOf'
+      ];
+
+      var memoized = _.memoize(identity);
+
+      var actual = lodashStable.map(props, function(value) {
+        return memoized(value);
+      });
+
+      assert.deepEqual(actual, props);
+    });
+
+    QUnit.test('should cache the `__proto__` key', function(assert) {
+      assert.expect(8);
+
+      var array = [],
+          key = '__proto__';
+
+      lodashStable.times(2, function(index) {
+        var count = 0,
+            resolver = index && identity;
+
+        var memoized = _.memoize(function() {
+          count++;
+          return array;
+        }, resolver);
+
+        var cache = memoized.cache;
+
+        memoized(key);
+        memoized(key);
+
+        assert.strictEqual(count, 1);
+        assert.strictEqual(cache.get(key), array);
+        assert.notOk(cache.__data__ instanceof Array);
+        assert.strictEqual(cache['delete'](key), true);
+      });
+    });
+
+    QUnit.test('should allow `_.memoize.Cache` to be customized', function(assert) {
+      assert.expect(4);
+
+      var oldCache = _.memoize.Cache;
+
+      function Cache() {
+        this.__data__ = [];
+      }
+
+      Cache.prototype = {
+        'get': function(key) {
+          var entry = _.find(this.__data__, function(entry) {
+            return key === entry.key;
+          });
+          return entry && entry.value;
+        },
+        'has': function(key) {
+          return _.some(this.__data__, function(entry) {
+            return key === entry.key;
+          });
+        },
+        'set': function(key, value) {
+          this.__data__.push({ 'key': key, 'value': value });
+          return this;
+        }
+      };
+
+      _.memoize.Cache = Cache;
+
+      var memoized = _.memoize(function(object) {
+        return 'value:' + object.id;
+      });
+
+      var cache = memoized.cache,
+          key1 = { 'id': 'a' },
+          key2 = { 'id': 'b' };
+
+      assert.strictEqual(memoized(key1), 'value:a');
+      assert.strictEqual(cache.has(key1), true);
+
+      assert.strictEqual(memoized(key2), 'value:b');
+      assert.strictEqual(cache.has(key2), true);
+
+      _.memoize.Cache = oldCache;
+    });
+
+    QUnit.test('should works with an immutable `_.memoize.Cache` ', function(assert) {
+      assert.expect(2);
+
+      var oldCache = _.memoize.Cache;
+
+      function Cache() {
+        this.__data__ = [];
+      }
+
+      Cache.prototype = {
+        'get': function(key) {
+          return _.find(this.__data__, function(entry) {
+            return key === entry.key;
+          }).value;
+        },
+        'has': function(key) {
+          return _.some(this.__data__, function(entry) {
+            return key === entry.key;
+          });
+        },
+        'set': function(key, value) {
+          var result = new Cache;
+          result.__data__ = this.__data__.concat({ 'key': key, 'value': value });
+          return result;
+        }
+      };
+
+      _.memoize.Cache = Cache;
+
+      var memoized = _.memoize(function(object) {
+        return object.id;
+      });
+
+      var key1 = { 'id': 'a' },
+          key2 = { 'id': 'b' };
+
+      memoized(key1);
+      memoized(key2);
+
+      var cache = memoized.cache;
+      assert.strictEqual(cache.has(key1), true);
+      assert.strictEqual(cache.has(key2), true);
+
+      _.memoize.Cache = oldCache;
+    });
+
+    QUnit.test('should implement a `Map` interface on the cache object', function(assert) {
+      assert.expect(164);
+
+      var keys = [null, undefined, false, true, 1, -Infinity, NaN, {}, 'a', symbol || {}];
+
+      var pairs = lodashStable.map(keys, function(key, index) {
+        var lastIndex = keys.length - 1;
+        return [key, keys[lastIndex - index]];
+      });
+
+      lodashStable.times(2, function(index) {
+        var memoize = (index ? (lodashBizarro || {}) : _).memoize,
+            Cache = memoize ? memoize.Cache : undefined,
+            cache = Cache ? new Cache(pairs) : undefined;
+
+        lodashStable.each(keys, function(key, index) {
+          if (cache) {
+            var value = pairs[index][1];
+
+            assert.deepEqual(cache.get(key), value);
+            assert.strictEqual(cache.has(key), true);
+            assert.strictEqual(cache['delete'](key), true);
+            assert.strictEqual(cache.has(key), false);
+            assert.strictEqual(cache.get(key), undefined);
+            assert.strictEqual(cache['delete'](key), false);
+            assert.strictEqual(cache.set(key, value), cache);
+            assert.strictEqual(cache.has(key), true);
+          }
+          else {
+            skipAssert(assert, 8);
+          }
+        });
+
+        if (cache) {
+          assert.strictEqual(cache.clear(), undefined);
+          assert.ok(lodashStable.every(keys, function(key) {
+            return !cache.has(key);
+          }));
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.merge');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should merge `source` into `object`', function(assert) {
+      assert.expect(1);
+
+      var names = {
+        'characters': [
+          { 'name': 'barney' },
+          { 'name': 'fred' }
+        ]
+      };
+
+      var ages = {
+        'characters': [
+          { 'age': 36 },
+          { 'age': 40 }
+        ]
+      };
+
+      var heights = {
+        'characters': [
+          { 'height': '5\'4"' },
+          { 'height': '5\'5"' }
+        ]
+      };
+
+      var expected = {
+        'characters': [
+          { 'name': 'barney', 'age': 36, 'height': '5\'4"' },
+          { 'name': 'fred', 'age': 40, 'height': '5\'5"' }
+        ]
+      };
+
+      assert.deepEqual(_.merge(names, ages, heights), expected);
+    });
+
+    QUnit.test('should merge sources containing circular references', function(assert) {
+      assert.expect(2);
+
+      var object = {
+        'foo': { 'a': 1 },
+        'bar': { 'a': 2 }
+      };
+
+      var source = {
+        'foo': { 'b': { 'c': { 'd': {} } } },
+        'bar': {}
+      };
+
+      source.foo.b.c.d = source;
+      source.bar.b = source.foo.b;
+
+      var actual = _.merge(object, source);
+
+      assert.notStrictEqual(actual.bar.b, actual.foo.b);
+      assert.strictEqual(actual.foo.b.c.d, actual.foo.b.c.d.foo.b.c.d);
+    });
+
+    QUnit.test('should work with four arguments', function(assert) {
+      assert.expect(1);
+
+      var expected = { 'a': 4 },
+          actual = _.merge({ 'a': 1 }, { 'a': 2 }, { 'a': 3 }, expected);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should merge onto function `object` values', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+
+      var source = { 'a': 1 },
+          actual = _.merge(Foo, source);
+
+      assert.strictEqual(actual, Foo);
+      assert.strictEqual(Foo.a, 1);
+    });
+
+    QUnit.test('should not merge onto nested function values', function(assert) {
+      assert.expect(3);
+
+      var source1 = { 'a': function() {} },
+          source2 = { 'a': { 'b': 1 } },
+          actual = _.merge({}, source1, source2),
+          expected = { 'a': { 'b': 1 } };
+
+      assert.deepEqual(actual, expected);
+
+      source1 = { 'a': function() {} };
+      source2 = { 'a': { 'b': 1 } };
+
+      expected = { 'a': function() {} };
+      expected.a.b = 1;
+
+      actual = _.merge(source1, source2);
+      assert.strictEqual(typeof actual.a, 'function');
+      assert.strictEqual(actual.a.b, 1);
+    });
+
+    QUnit.test('should merge onto non-plain `object` values', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+
+      var object = new Foo,
+          actual = _.merge(object, { 'a': 1 });
+
+      assert.strictEqual(actual, object);
+      assert.strictEqual(object.a, 1);
+    });
+
+    QUnit.test('should treat sparse array sources as dense', function(assert) {
+      assert.expect(2);
+
+      var array = [1];
+      array[2] = 3;
+
+      var actual = _.merge([], array),
+          expected = array.slice();
+
+      expected[1] = undefined;
+
+      assert.ok('1' in actual);
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should merge `arguments` objects', function(assert) {
+      assert.expect(7);
+
+      var object1 = { 'value': args },
+          object2 = { 'value': { '3': 4 } },
+          expected = { '0': 1, '1': 2, '2': 3, '3': 4 },
+          actual = _.merge(object1, object2);
+
+      assert.notOk('3' in args);
+      assert.notOk(_.isArguments(actual.value));
+      assert.deepEqual(actual.value, expected);
+      object1.value = args;
+
+      actual = _.merge(object2, object1);
+      assert.notOk(_.isArguments(actual.value));
+      assert.deepEqual(actual.value, expected);
+
+      expected = { '0': 1, '1': 2, '2': 3 };
+
+      actual = _.merge({}, object1);
+      assert.notOk(_.isArguments(actual.value));
+      assert.deepEqual(actual.value, expected);
+    });
+
+    QUnit.test('should merge typed arrays', function(assert) {
+      assert.expect(4);
+
+      var array1 = [0],
+          array2 = [0, 0],
+          array3 = [0, 0, 0, 0],
+          array4 = [0, 0, 0, 0, 0, 0, 0, 0];
+
+      var arrays = [array2, array1, array4, array3, array2, array4, array4, array3, array2],
+          buffer = ArrayBuffer && new ArrayBuffer(8);
+
+      // Juggle for `Float64Array` shim.
+      if (root.Float64Array && (new Float64Array(buffer)).length == 8) {
+        arrays[1] = array4;
+      }
+      var expected = lodashStable.map(typedArrays, function(type, index) {
+        var array = arrays[index].slice();
+        array[0] = 1;
+        return root[type] ? { 'value': array } : false;
+      });
+
+      var actual = lodashStable.map(typedArrays, function(type) {
+        var Ctor = root[type];
+        return Ctor ? _.merge({ 'value': new Ctor(buffer) }, { 'value': [1] }) : false;
+      });
+
+      assert.ok(lodashStable.isArray(actual));
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(typedArrays, function(type, index) {
+        var array = arrays[index].slice();
+        array.push(1);
+        return root[type] ? { 'value': array } : false;
+      });
+
+      actual = lodashStable.map(typedArrays, function(type, index) {
+        var Ctor = root[type],
+            array = lodashStable.range(arrays[index].length);
+
+        array.push(1);
+        return Ctor ? _.merge({ 'value': array }, { 'value': new Ctor(buffer) }) : false;
+      });
+
+      assert.ok(lodashStable.isArray(actual));
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should assign `null` values', function(assert) {
+      assert.expect(1);
+
+      var actual = _.merge({ 'a': 1 }, { 'a': null });
+      assert.strictEqual(actual.a, null);
+    });
+
+    QUnit.test('should assign non array/typed-array/plain-object sources directly', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+
+      var values = [new Foo, new Boolean, new Date, Foo, new Number, new String, new RegExp],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        var object = _.merge({}, { 'value': value });
+        return object.value === value;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should deep clone array/typed-array/plain-object sources', function(assert) {
+      assert.expect(1);
+
+      var typedArray = Uint8Array
+        ? new Uint8Array(new ArrayBuffer(2))
+        : { 'buffer': [0, 0] };
+
+      var props = ['0', 'a', 'buffer'],
+          values = [[{ 'a': 1 }], { 'a': [1] }, typedArray],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var key = props[index],
+            object = _.merge({}, { 'value': value }),
+            newValue = object.value;
+
+        return (
+          newValue !== value &&
+          newValue[key] !== value[key] &&
+          lodashStable.isEqual(newValue, value)
+        );
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not augment source objects', function(assert) {
+      assert.expect(6);
+
+      var source1 = { 'a': [{ 'a': 1 }] },
+          source2 = { 'a': [{ 'b': 2 }] },
+          actual = _.merge({}, source1, source2);
+
+      assert.deepEqual(source1.a, [{ 'a': 1 }]);
+      assert.deepEqual(source2.a, [{ 'b': 2 }]);
+      assert.deepEqual(actual.a, [{ 'a': 1, 'b': 2 }]);
+
+      var source1 = { 'a': [[1, 2, 3]] },
+          source2 = { 'a': [[3, 4]] },
+          actual = _.merge({}, source1, source2);
+
+      assert.deepEqual(source1.a, [[1, 2, 3]]);
+      assert.deepEqual(source2.a, [[3, 4]]);
+      assert.deepEqual(actual.a, [[3, 4, 3]]);
+    });
+
+    QUnit.test('should merge plain-objects onto non plain-objects', function(assert) {
+      assert.expect(4);
+
+      function Foo(object) {
+        lodashStable.assign(this, object);
+      }
+
+      var object = { 'a': 1 },
+          actual = _.merge(new Foo, object);
+
+      assert.ok(actual instanceof Foo);
+      assert.deepEqual(actual, new Foo(object));
+
+      actual = _.merge([new Foo], [object]);
+      assert.ok(actual[0] instanceof Foo);
+      assert.deepEqual(actual, [new Foo(object)]);
+    });
+
+    QUnit.test('should not assign `undefined` values', function(assert) {
+      assert.expect(1);
+
+      var actual = _.merge({ 'a': 1 }, { 'a': undefined, 'b': undefined });
+      assert.deepEqual(actual, { 'a': 1 });
+    });
+
+    QUnit.test('should skip `undefined` values in array sources if a destination value exists', function(assert) {
+      assert.expect(2);
+
+      var array = [1];
+      array[2] = 3;
+
+      var actual = _.merge([4, 5, 6], array),
+          expected = [1, 5, 3];
+
+      assert.deepEqual(actual, expected);
+
+      array = [1, , 3];
+      array[1] = undefined;
+
+      actual = _.merge([4, 5, 6], array);
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should skip merging when `object` and `source` are the same value', function(assert) {
+      assert.expect(1);
+
+      if (defineProperty) {
+        var object = {},
+            pass = true;
+
+        defineProperty(object, 'a', {
+          'enumerable': true,
+          'configurable': true,
+          'get': function() { pass = false; },
+          'set': function() { pass = false; }
+        });
+
+        _.merge(object, object);
+        assert.ok(pass);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should convert values to arrays when merging arrays of `source`', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { '1': 'y', 'b': 'z', 'length': 2 } },
+          actual = _.merge(object, { 'a': ['x'] });
+
+      assert.deepEqual(actual, { 'a': ['x', 'y'] });
+
+      actual = _.merge({ 'a': {} }, { 'a': [] });
+      assert.deepEqual(actual, { 'a': [] });
+    });
+
+    QUnit.test('should not convert strings to arrays when merging arrays of `source`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 'abcde' },
+          actual = _.merge(object, { 'a': ['x', 'y', 'z'] });
+
+      assert.deepEqual(actual, { 'a': ['x', 'y', 'z'] });
+    });
+
+    QUnit.test('should not error on DOM elements', function(assert) {
+      assert.expect(1);
+
+      var object1 = { 'el': document && document.createElement('div') },
+          object2 = { 'el': document && document.createElement('div') },
+          pairs = [[{}, object1], [object1, object2]],
+          expected = lodashStable.map(pairs, alwaysTrue);
+
+      var actual = lodashStable.map(pairs, function(pair) {
+        try {
+          return _.merge(pair[0], pair[1]).el === pair[1].el;
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.mergeWith');
+
+  (function() {
+    QUnit.test('should handle merging if `customizer` returns `undefined`', function(assert) {
+      assert.expect(2);
+
+      var actual = _.mergeWith({ 'a': { 'b': [1, 1] } }, { 'a': { 'b': [0] } }, noop);
+      assert.deepEqual(actual, { 'a': { 'b': [0, 1] } });
+
+      actual = _.mergeWith([], [undefined], identity);
+      assert.deepEqual(actual, [undefined]);
+    });
+
+    QUnit.test('should defer to `customizer` when it returns a non `undefined` value', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mergeWith({ 'a': { 'b': [0, 1] } }, { 'a': { 'b': [2] } }, function(a, b) {
+        return lodashStable.isArray(a) ? a.concat(b) : undefined;
+      });
+
+      assert.deepEqual(actual, { 'a': { 'b': [0, 1, 2] } });
+    });
+
+    QUnit.test('should overwrite primitives with source object clones', function(assert) {
+      assert.expect(1);
+
+      var actual = _.mergeWith({ 'a': 0 }, { 'a': { 'b': ['c'] } }, function(a, b) {
+        return lodashStable.isArray(a) ? a.concat(b) : undefined;
+      });
+
+      assert.deepEqual(actual, { 'a': { 'b': ['c'] } });
+    });
+
+    QUnit.test('should clone sources when `customizer` result is `undefined`', function(assert) {
+      assert.expect(1);
+
+      var source1 = { 'a': { 'b': { 'c': 1 } } },
+          source2 = { 'a': { 'b': { 'd': 2 } } };
+
+      _.mergeWith({}, source1, source2, noop);
+      assert.deepEqual(source1.a.b, { 'c': 1 });
+    });
+
+    QUnit.test('should pop the stack of sources for each sibling property', function(assert) {
+      assert.expect(1);
+
+      var array = ['b', 'c'],
+          object = { 'a': ['a'] },
+          source = { 'a': array, 'b': array };
+
+      var actual = _.mergeWith(object, source, function(a, b) {
+        return lodashStable.isArray(a) ? a.concat(b) : undefined;
+      });
+
+      assert.deepEqual(actual, { 'a': ['a', 'b', 'c'], 'b': ['b', 'c'] });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.method');
+
+  (function() {
+    QUnit.test('should create a function that calls a method of a given object', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': alwaysOne };
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method.length, 1);
+        assert.strictEqual(method(object), 1);
+      });
+    });
+
+    QUnit.test('should work with deep property values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': alwaysTwo } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method(object), 2);
+      });
+    });
+
+    QUnit.test('should work with a non-string `path`', function(assert) {
+      assert.expect(2);
+
+      var array = lodashStable.times(3, _.constant);
+
+      lodashStable.each([1, [1]], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method(array), 1);
+      });
+    });
+
+    QUnit.test('should coerce key to a string', function(assert) {
+      assert.expect(1);
+
+      function fn() {}
+      fn.toString = lodashStable.constant('fn');
+
+      var expected = [1, 1, 2, 2, 3, 3, 4, 4],
+          objects = [{ 'null': alwaysOne }, { 'undefined': alwaysTwo }, { 'fn': alwaysThree }, { '[object Object]': alwaysFour }],
+          values = [null, undefined, fn, {}];
+
+      var actual = lodashStable.transform(objects, function(result, object, index) {
+        var key = values[index];
+        lodashStable.each([key, [key]], function(path) {
+          var method = _.method(key);
+          result.push(method(object));
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with inherited property values', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.a = alwaysOne;
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method(new Foo), 1);
+      });
+    });
+
+    QUnit.test('should use a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': alwaysOne, 'a': { 'b': alwaysTwo } };
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method(object), 1);
+      });
+    });
+
+    QUnit.test('should return `undefined` when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        var method = _.method(path);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? method(value) : method();
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` with deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) {
+        var method = _.method(path);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? method(value) : method();
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` if parts of `path` are missing', function(assert) {
+      assert.expect(4);
+
+      var object = {};
+
+      lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method(object), undefined);
+      });
+    });
+
+    QUnit.test('should apply partial arguments to function', function(assert) {
+      assert.expect(2);
+
+      var object = {
+        'fn': function() {
+          return slice.call(arguments);
+        }
+      };
+
+      lodashStable.each(['fn', ['fn']], function(path) {
+        var method = _.method(path, 1, 2, 3);
+        assert.deepEqual(method(object), [1, 2, 3]);
+      });
+    });
+
+    QUnit.test('should invoke deep property methods with the correct `this` binding', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var method = _.method(path);
+        assert.strictEqual(method(object), 1);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.methodOf');
+
+  (function() {
+    QUnit.test('should create a function that calls a method of a given key', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': alwaysOne };
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var methodOf = _.methodOf(object);
+        assert.strictEqual(methodOf.length, 1);
+        assert.strictEqual(methodOf(path), 1);
+      });
+    });
+
+    QUnit.test('should work with deep property values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': alwaysTwo } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var methodOf = _.methodOf(object);
+        assert.strictEqual(methodOf(path), 2);
+      });
+    });
+
+    QUnit.test('should work with a non-string `path`', function(assert) {
+      assert.expect(2);
+
+      var array = lodashStable.times(3, _.constant);
+
+      lodashStable.each([1, [1]], function(path) {
+        var methodOf = _.methodOf(array);
+        assert.strictEqual(methodOf(path), 1);
+      });
+    });
+
+    QUnit.test('should coerce key to a string', function(assert) {
+      assert.expect(1);
+
+      function fn() {}
+      fn.toString = lodashStable.constant('fn');
+
+      var expected = [1, 1, 2, 2, 3, 3, 4, 4],
+          objects = [{ 'null': alwaysOne }, { 'undefined': alwaysTwo }, { 'fn': alwaysThree }, { '[object Object]': alwaysFour }],
+          values = [null, undefined, fn, {}];
+
+      var actual = lodashStable.transform(objects, function(result, object, index) {
+        var key = values[index];
+        lodashStable.each([key, [key]], function(path) {
+          var methodOf = _.methodOf(object);
+          result.push(methodOf(key));
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with inherited property values', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.a = alwaysOne;
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var methodOf = _.methodOf(new Foo);
+        assert.strictEqual(methodOf(path), 1);
+      });
+    });
+
+    QUnit.test('should use a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': alwaysOne, 'a': { 'b': alwaysTwo } };
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        var methodOf = _.methodOf(object);
+        assert.strictEqual(methodOf(path), 1);
+      });
+    });
+
+    QUnit.test('should return `undefined` when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        var actual = lodashStable.map(values, function(value, index) {
+          var methodOf = index ? _.methodOf() : _.methodOf(value);
+          return methodOf(path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` with deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) {
+        var actual = lodashStable.map(values, function(value, index) {
+          var methodOf = index ? _.methodOf() : _.methodOf(value);
+          return methodOf(path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` if parts of `path` are missing', function(assert) {
+      assert.expect(4);
+
+      var object = {},
+          methodOf = _.methodOf(object);
+
+      lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) {
+        assert.strictEqual(methodOf(path), undefined);
+      });
+    });
+
+    QUnit.test('should apply partial arguments to function', function(assert) {
+      assert.expect(2);
+
+      var object = {
+        'fn': function() {
+          return slice.call(arguments);
+        }
+      };
+
+      var methodOf = _.methodOf(object, 1, 2, 3);
+
+      lodashStable.each(['fn', ['fn']], function(path) {
+        assert.deepEqual(methodOf(path), [1, 2, 3]);
+      });
+    });
+
+    QUnit.test('should invoke deep property methods with the correct `this` binding', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': function() { return this.c; }, 'c': 1 } },
+          methodOf = _.methodOf(object);
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(methodOf(path), 1);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.min');
+
+  (function() {
+    QUnit.test('should return the smallest value from a collection', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.min([1, 2, 3]), 1);
+    });
+
+    QUnit.test('should return `undefined` for empty collections', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat([[]]),
+          expected = lodashStable.map(values, noop);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        try {
+          return index ? _.min(value) : _.min();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with non-numeric collection values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.min(['a', 'b']), 'a');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('extremum methods');
+
+  lodashStable.each(['max', 'maxBy', 'min', 'minBy'], function(methodName) {
+    var func = _[methodName],
+        isMax = /^max/.test(methodName);
+
+    QUnit.test('`_.' + methodName + '` should work with Date objects', function(assert) {
+      assert.expect(1);
+
+      var curr = new Date,
+          past = new Date(0);
+
+      assert.strictEqual(func([curr, past]), isMax ? curr : past);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with extremely large arrays', function(assert) {
+      assert.expect(1);
+
+      var array = lodashStable.range(0, 5e5);
+      assert.strictEqual(func(array), isMax ? 499999 : 0);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work when chaining on an array with only one value', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = _([40])[methodName]();
+        assert.strictEqual(actual, 40);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  lodashStable.each(['maxBy', 'minBy'], function(methodName) {
+    var array = [1, 2, 3],
+        func = _[methodName],
+        isMax = methodName == 'maxBy';
+
+    QUnit.test('`_.' + methodName + '` should work with an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var actual = func(array, function(n) {
+        return -n;
+      });
+
+      assert.strictEqual(actual, isMax ? 1 : 3);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }],
+          actual = func(objects, 'a');
+
+      assert.deepEqual(actual, objects[isMax ? 1 : 2]);
+
+      var arrays = [[2], [3], [1]];
+      actual = func(arrays, 0);
+
+      assert.deepEqual(actual, arrays[isMax ? 1 : 2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work when `iteratee` returns +/-Infinity', function(assert) {
+      assert.expect(1);
+
+      var value = isMax ? -Infinity : Infinity,
+          object = { 'a': value };
+
+      var actual = func([object, { 'a': value }], function(object) {
+        return object.a;
+      });
+
+      assert.strictEqual(actual, object);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.mixin');
+
+  (function() {
+    function reset(wrapper) {
+      delete wrapper.a;
+      delete wrapper.prototype.a;
+      delete wrapper.b;
+      delete wrapper.prototype.b;
+    }
+
+    function Wrapper(value) {
+      if (!(this instanceof Wrapper)) {
+        return new Wrapper(value);
+      }
+      if (_.has(value, '__wrapped__')) {
+        var actions = slice.call(value.__actions__),
+            chain = value.__chain__;
+
+        value = value.__wrapped__;
+      }
+      this.__wrapped__ = value;
+      this.__actions__ = actions || [];
+      this.__chain__ = chain || false;
+    }
+
+    Wrapper.prototype.value = function() {
+      return getUnwrappedValue(this);
+    };
+
+    var array = ['a'],
+        source = { 'a': function(array) { return array[0]; }, 'b': 'B' };
+
+    QUnit.test('should mixin `source` methods into lodash', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        _.mixin(source);
+
+        assert.strictEqual(_.a(array), 'a');
+        assert.strictEqual(_(array).a().value(), 'a');
+        assert.notOk('b' in _);
+        assert.notOk('b' in _.prototype);
+
+        reset(_);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should mixin chaining methods by reference', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        _.mixin(source);
+        _.a = alwaysB;
+
+        assert.strictEqual(_.a(array), 'b');
+        assert.strictEqual(_(array).a().value(), 'a');
+
+        reset(_);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should use a default `object` of `this`', function(assert) {
+      assert.expect(3);
+
+      var object = lodashStable.create(_);
+      object.mixin(source);
+
+      assert.strictEqual(object.a(array), 'a');
+      assert.notOk('a' in _);
+      assert.notOk('a' in _.prototype);
+
+      reset(_);
+    });
+
+    QUnit.test('should accept an `object` argument', function(assert) {
+      assert.expect(1);
+
+      var object = {};
+      _.mixin(object, source);
+      assert.strictEqual(object.a(array), 'a');
+    });
+
+    QUnit.test('should accept a function `object`', function(assert) {
+      assert.expect(2);
+
+      _.mixin(Wrapper, source);
+
+      var wrapped = Wrapper(array),
+          actual = wrapped.a();
+
+      assert.strictEqual(actual.value(), 'a');
+      assert.ok(actual instanceof Wrapper);
+
+      reset(Wrapper);
+    });
+
+    QUnit.test('should return `object`', function(assert) {
+      assert.expect(3);
+
+      var object = {};
+      assert.strictEqual(_.mixin(object, source), object);
+      assert.strictEqual(_.mixin(Wrapper, source), Wrapper);
+      assert.strictEqual(_.mixin(), _);
+
+      reset(Wrapper);
+    });
+
+    QUnit.test('should not assign inherited `source` methods', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype.a = noop;
+
+      var object = {};
+      assert.strictEqual(_.mixin(object, new Foo), object);
+    });
+
+    QUnit.test('should accept an `options` argument', function(assert) {
+      assert.expect(8);
+
+      function message(func, chain) {
+        return (func === _ ? 'lodash' : 'given') + ' function should ' + (chain ? '' : 'not ') + 'chain';
+      }
+
+      lodashStable.each([_, Wrapper], function(func) {
+        lodashStable.each([{ 'chain': false }, { 'chain': true }], function(options) {
+          if (!isNpm) {
+            if (func === _) {
+              _.mixin(source, options);
+            } else {
+              _.mixin(func, source, options);
+            }
+            var wrapped = func(array),
+                actual = wrapped.a();
+
+            if (options.chain) {
+              assert.strictEqual(actual.value(), 'a', message(func, true));
+              assert.ok(actual instanceof func, message(func, true));
+            } else {
+              assert.strictEqual(actual, 'a', message(func, false));
+              assert.notOk(actual instanceof func, message(func, false));
+            }
+            reset(func);
+          }
+          else {
+            skipAssert(assert, 2);
+          }
+        });
+      });
+    });
+
+    QUnit.test('should not extend lodash when an `object` is given with an empty `options` object', function(assert) {
+      assert.expect(1);
+
+      _.mixin({ 'a': noop }, {});
+      assert.notOk('a' in _);
+      reset(_);
+    });
+
+    QUnit.test('should not error for non-object `options` values', function(assert) {
+      assert.expect(2);
+
+      var pass = true;
+
+      try {
+        _.mixin({}, source, 1);
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass);
+
+      pass = true;
+
+      try {
+        _.mixin(source, 1);
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass);
+
+      reset(_);
+    });
+
+    QUnit.test('should not return the existing wrapped value when chaining', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([_, Wrapper], function(func) {
+        if (!isNpm) {
+          if (func === _) {
+            var wrapped = _(source),
+                actual = wrapped.mixin();
+
+            assert.strictEqual(actual.value(), _);
+          }
+          else {
+            wrapped = _(func);
+            actual = wrapped.mixin(source);
+            assert.notStrictEqual(actual, wrapped);
+          }
+          reset(func);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    QUnit.test('should produce methods that work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        _.mixin({ 'a': _.countBy, 'b': _.filter });
+
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            actual = _(array).a().map(square).b(isEven).take().value();
+
+        assert.deepEqual(actual, _.take(_.b(_.map(_.a(array), square), isEven)));
+
+        reset(_);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.multiply');
+
+  (function() {
+    QUnit.test('should multiply two numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.multiply(6, 4), 24);
+      assert.strictEqual(_.multiply(-6, 4), -24);
+      assert.strictEqual(_.multiply(-6, -4), 24);
+    });
+
+    QUnit.test('should coerce arguments to numbers', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.multiply('6', '4'), 24);
+      assert.deepEqual(_.multiply('x', 'y'), NaN);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.orderBy');
+
+  (function() {
+    var objects = [
+      { 'a': 'x', 'b': 3 },
+      { 'a': 'y', 'b': 4 },
+      { 'a': 'x', 'b': 1 },
+      { 'a': 'y', 'b': 2 }
+    ];
+
+    QUnit.test('should sort by a single property by a specified order', function(assert) {
+      assert.expect(1);
+
+      var actual = _.orderBy(objects, 'a', 'desc');
+      assert.deepEqual(actual, [objects[1], objects[3], objects[0], objects[2]]);
+    });
+
+    QUnit.test('should sort by multiple properties by specified orders', function(assert) {
+      assert.expect(1);
+
+      var actual = _.orderBy(objects, ['a', 'b'], ['desc', 'asc']);
+      assert.deepEqual(actual, [objects[3], objects[1], objects[2], objects[0]]);
+    });
+
+    QUnit.test('should sort by a property in ascending order when its order is not specified', function(assert) {
+      assert.expect(2);
+
+      var expected = [objects[2], objects[0], objects[3], objects[1]],
+          actual = _.orderBy(objects, ['a', 'b']);
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(falsey, lodashStable.constant([objects[3], objects[1], objects[2], objects[0]]));
+
+      actual = lodashStable.map(falsey, function(order, index) {
+        return _.orderBy(objects, ['a', 'b'], index ? ['desc', order] : ['desc']);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `orders` specified as string objects', function(assert) {
+      assert.expect(1);
+
+      var actual = _.orderBy(objects, ['a'], [Object('desc')]);
+      assert.deepEqual(actual, [objects[1], objects[3], objects[0], objects[2]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.overArgs');
+
+  (function() {
+    function fn() {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should transform each argument', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, doubled, square);
+      assert.deepEqual(over(5, 10), [10, 100]);
+    });
+
+    QUnit.test('should use `_.identity` when a predicate is nullish', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, undefined, null);
+      assert.deepEqual(over('a', 'b'), ['a', 'b']);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, 'b', 'a');
+      assert.deepEqual(over({ 'b': 2 }, { 'a': 1 }), [2, 1]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, { 'b': 1 }, { 'a': 1 });
+      assert.deepEqual(over({ 'b': 2 }, { 'a': 1 }), [false, true]);
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, ['b', 1], [['a', 1]]);
+      assert.deepEqual(over({ 'b': 2 }, { 'a': 1 }), [false, true]);
+    });
+
+    QUnit.test('should differentiate between `_.property` and `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overArgs(fn, ['a', 1]);
+      assert.deepEqual(over({ 'a': 1 }, { '1': 2 }), [1, 2]);
+
+      over = _.overArgs(fn, [['a', 1]]);
+      assert.deepEqual(over({ 'a': 1 }), [true]);
+    });
+
+    QUnit.test('should flatten `transforms`', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, [doubled, square], String);
+      assert.deepEqual(over(5, 10, 15), [10, 100, '15']);
+    });
+
+    QUnit.test('should not transform any argument greater than the number of transforms', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, doubled, square);
+      assert.deepEqual(over(5, 10, 18), [10, 100, 18]);
+    });
+
+    QUnit.test('should not transform any arguments if no transforms are given', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn);
+      assert.deepEqual(over(5, 10, 18), [5, 10, 18]);
+    });
+
+    QUnit.test('should not pass `undefined` if there are more transforms than arguments', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(fn, doubled, identity);
+      assert.deepEqual(over(5), [10]);
+    });
+
+    QUnit.test('should provide the correct argument to each transform', function(assert) {
+      assert.expect(1);
+
+      var argsList = [],
+          transform = function() { argsList.push(slice.call(arguments)); },
+          over = _.overArgs(noop, transform, transform, transform);
+
+      over('a', 'b');
+      assert.deepEqual(argsList, [['a'], ['b']]);
+    });
+
+    QUnit.test('should use `this` binding of function for `transforms`', function(assert) {
+      assert.expect(1);
+
+      var over = _.overArgs(function(x) {
+        return this[x];
+      }, function(x) {
+        return this === x;
+      });
+
+      var object = { 'over': over, 'true': 1 };
+      assert.strictEqual(object.over(object), 1);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.negate');
+
+  (function() {
+    QUnit.test('should create a function that negates the result of `func`', function(assert) {
+      assert.expect(2);
+
+      var negate = _.negate(isEven);
+
+      assert.strictEqual(negate(1), true);
+      assert.strictEqual(negate(2), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.noop');
+
+  (function() {
+    QUnit.test('should return `undefined`', function(assert) {
+      assert.expect(1);
+
+      var values = empties.concat(true, new Date, _, 1, /x/, 'a'),
+          expected = lodashStable.map(values, noop);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.noop(value) : _.noop();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.noConflict');
+
+  (function() {
+    QUnit.test('should return the `lodash` function', function(assert) {
+      assert.expect(2);
+
+      if (!isModularize) {
+        assert.strictEqual(_.noConflict(), oldDash);
+        assert.notStrictEqual(root._, oldDash);
+        root._ = oldDash;
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should restore `_` only if `lodash` is the current `_` value', function(assert) {
+      assert.expect(2);
+
+      if (!isModularize) {
+        var object = root._ = {};
+        assert.strictEqual(_.noConflict(), oldDash);
+        assert.strictEqual(root._, object);
+        root._ = oldDash;
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should work with a `root` of `this`', function(assert) {
+      assert.expect(2);
+
+      if (!isModularize && !coverage && (!document && realm.object)) {
+        var fs = require('fs'),
+            vm = require('vm'),
+            expected = {},
+            context = vm.createContext({ '_': expected, 'console': console }),
+            source = fs.readFileSync(filePath, 'utf8');
+
+        vm.runInContext(source + '\nthis.lodash = this._.noConflict()', context);
+
+        assert.strictEqual(context._, expected);
+        assert.ok(context.lodash);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.now');
+
+  (function() {
+    QUnit.test('should return the number of milliseconds that have elapsed since the Unix epoch', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var stamp = +new Date,
+          actual = _.now();
+
+      assert.ok(actual >= stamp);
+
+      setTimeout(function() {
+        assert.ok(_.now() > actual);
+        done();
+      }, 32);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.nth');
+
+  (function() {
+    var array = ['a', 'b', 'c', 'd'];
+
+    QUnit.test('should get the nth element of `array`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(array, function(value, index) {
+        return _.nth(array, index);
+      });
+
+      assert.deepEqual(actual, array);
+    });
+
+    QUnit.test('should work with a negative `n`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(lodashStable.range(1, array.length + 1), function(n) {
+        return _.nth(array, -n);
+      });
+
+      assert.deepEqual(actual, ['d', 'c', 'b', 'a']);
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(2);
+
+      var values = falsey,
+          expected = lodashStable.map(values, alwaysA);
+
+      var actual = lodashStable.map(values, function(n) {
+        return n ? _.nth(array, n) : _.nth(array);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      values = ['1', 1.6];
+      expected = lodashStable.map(values, alwaysB);
+
+      actual = lodashStable.map(values, function(n) {
+        return _.nth(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `undefined` for empty arrays', function(assert) {
+      assert.expect(1);
+
+      var values = [null, undefined, []],
+          expected = lodashStable.map(values, noop);
+
+      var actual = lodashStable.map(values, function(array) {
+        return _.nth(array, 1);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `undefined` for non-indexes', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2],
+          values = [Infinity, array.length],
+          expected = lodashStable.map(values, noop);
+
+      array[-1] = 3;
+
+      var actual = lodashStable.map(values, function(n) {
+        return _.nth(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.nthArg');
+
+  (function() {
+    var args = ['a', 'b', 'c', 'd'];
+
+    QUnit.test('should create a function that returns its nth argument', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(args, function(value, index) {
+        var func = _.nthArg(index);
+        return func.apply(undefined, args);
+      });
+
+      assert.deepEqual(actual, args);
+    });
+
+    QUnit.test('should work with a negative `n`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(lodashStable.range(1, args.length + 1), function(n) {
+        var func = _.nthArg(-n);
+        return func.apply(undefined, args);
+      });
+
+      assert.deepEqual(actual, ['d', 'c', 'b', 'a']);
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(2);
+
+      var values = falsey,
+          expected = lodashStable.map(values, alwaysA);
+
+      var actual = lodashStable.map(values, function(n) {
+        var func = n ? _.nthArg(n) : _.nthArg();
+        return func.apply(undefined, args);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      values = ['1', 1.6];
+      expected = lodashStable.map(values, alwaysB);
+
+      actual = lodashStable.map(values, function(n) {
+        var func = _.nthArg(n);
+        return func.apply(undefined, args);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `undefined` for empty arrays', function(assert) {
+      assert.expect(1);
+
+      var func = _.nthArg(1);
+      assert.strictEqual(func(), undefined);
+    });
+
+    QUnit.test('should return `undefined` for non-indexes', function(assert) {
+      assert.expect(1);
+
+      var values = [Infinity, args.length],
+          expected = lodashStable.map(values, noop);
+
+      var actual = lodashStable.map(values, function(n) {
+        var func = _.nthArg(n);
+        return func.apply(undefined, args);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.omit');
+
+  (function() {
+    var args = arguments,
+        object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 };
+
+    QUnit.test('should flatten `props`', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.omit(object, 'a', 'c'), { 'b': 2, 'd': 4 });
+      assert.deepEqual(_.omit(object, ['a', 'd'], 'c'), { 'b': 2 });
+    });
+
+    QUnit.test('should work with a primitive `object` argument', function(assert) {
+      assert.expect(1);
+
+      stringProto.a = 1;
+      stringProto.b = 2;
+
+      assert.deepEqual(_.omit('', 'b'), { 'a': 1 });
+
+      delete stringProto.a;
+      delete stringProto.b;
+    });
+
+    QUnit.test('should return an empty object when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      objectProto.a = 1;
+      lodashStable.each([null, undefined], function(value) {
+        assert.deepEqual(_.omit(value, 'valueOf'), {});
+      });
+      delete objectProto.a;
+    });
+
+    QUnit.test('should work with `arguments` objects as secondary arguments', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.omit(object, args), { 'b': 2, 'd': 4 });
+    });
+
+    QUnit.test('should coerce property names to strings', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.omit({ '0': 'a' }, 0), {});
+    });
+  }('a', 'c'));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.omitBy');
+
+  (function() {
+    QUnit.test('should work with a predicate argument', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 };
+
+      var actual = _.omitBy(object, function(n) {
+        return n != 2 && n != 4;
+      });
+
+      assert.deepEqual(actual, { 'b': 2, 'd': 4 });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('omit methods');
+
+  lodashStable.each(['omit', 'omitBy'], function(methodName) {
+    var expected = { 'b': 2, 'd': 4 },
+        func = _[methodName],
+        object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 },
+        prop = lodashStable.nthArg(1);
+
+    if (methodName == 'omitBy') {
+      prop = function(object, props) {
+        props = lodashStable.castArray(props);
+        return function(value) {
+          return lodashStable.some(props, function(key) {
+            key = lodashStable.isSymbol(key) ? key : lodashStable.toString(key);
+            return object[key] === value;
+          });
+        };
+      };
+    }
+    QUnit.test('`_.' + methodName + '` should create an object with omitted string keyed properties', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(func(object, prop(object, 'a')), { 'b': 2, 'c': 3, 'd': 4 });
+      assert.deepEqual(func(object, prop(object, ['a', 'c'])), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should include inherited string keyed properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype = object;
+
+      assert.deepEqual(func(new Foo, prop(object, ['a', 'c'])), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': 'a', '0': 'b' },
+          props = [-0, Object(-0), 0, Object(0)],
+          expected = [{ '0': 'b' }, { '0': 'b' }, { '-0': 'a' }, { '-0': 'a' }];
+
+      var actual = lodashStable.map(props, function(key) {
+        return func(object, prop(object, key));
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should include symbol properties', function(assert) {
+      assert.expect(2);
+
+      function Foo() {
+        this.a = 0;
+        this[symbol] = 1;
+      }
+
+      if (Symbol) {
+        var symbol2 = Symbol('b');
+        Foo.prototype[symbol2] = 2;
+
+        var foo = new Foo,
+            actual = func(foo, prop(foo, 'a'));
+
+        assert.strictEqual(actual[symbol], 1);
+        assert.strictEqual(actual[symbol2], 2);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should create an object with omitted symbol properties', function(assert) {
+      assert.expect(6);
+
+      function Foo() {
+        this.a = 0;
+        this[symbol] = 1;
+      }
+
+      if (Symbol) {
+        var symbol2 = Symbol('b');
+        Foo.prototype[symbol2] = 2;
+
+        var foo = new Foo,
+            actual = func(foo, prop(foo, symbol));
+
+        assert.strictEqual(actual.a, 0);
+        assert.strictEqual(actual[symbol], undefined);
+        assert.strictEqual(actual[symbol2], 2);
+
+        actual = func(foo, prop(foo, symbol2));
+
+        assert.strictEqual(actual.a, 0);
+        assert.strictEqual(actual[symbol], 1);
+        assert.strictEqual(actual[symbol2], undefined);
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with an array `object` argument', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(func(array, prop(array, ['0', '2'])), { '1': 2 });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.once');
+
+  (function() {
+    QUnit.test('should invoke `func` once', function(assert) {
+      assert.expect(2);
+
+      var count = 0,
+          once = _.once(function() { return ++count; });
+
+      once();
+      assert.strictEqual(once(), 1);
+      assert.strictEqual(count, 1);
+    });
+
+    QUnit.test('should ignore recursive calls', function(assert) {
+      assert.expect(2);
+
+      var count = 0;
+
+      var once = _.once(function() {
+        once();
+        return ++count;
+      });
+
+      assert.strictEqual(once(), 1);
+      assert.strictEqual(count, 1);
+    });
+
+    QUnit.test('should not throw more than once', function(assert) {
+      assert.expect(2);
+
+      var pass = true;
+
+      var once = _.once(function() {
+        throw new Error;
+      });
+
+      assert.raises(once);
+
+      try {
+        once();
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.over');
+
+  (function() {
+    QUnit.test('should create a function that invokes `iteratees`', function(assert) {
+      assert.expect(1);
+
+      var over = _.over(Math.max, Math.min);
+      assert.deepEqual(over(1, 2, 3, 4), [4, 1]);
+    });
+
+    QUnit.test('should use `_.identity` when a predicate is nullish', function(assert) {
+      assert.expect(1);
+
+      var over = _.over(undefined, null);
+      assert.deepEqual(over('a', 'b', 'c'), ['a', 'a']);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var over = _.over('b', 'a');
+      assert.deepEqual(over({ 'a': 1, 'b': 2 }), [2, 1]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      var over = _.over({ 'b': 1 }, { 'a': 1 });
+      assert.deepEqual(over({ 'a': 1, 'b': 2 }), [false, true]);
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.over(['b', 2], [['a', 2]]);
+
+      assert.deepEqual(over({ 'a': 1, 'b': 2 }), [true, false]);
+      assert.deepEqual(over({ 'a': 2, 'b': 1 }), [false, true]);
+    });
+
+    QUnit.test('should differentiate between `_.property` and `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(4);
+
+      var over = _.over(['a', 1]);
+
+      assert.deepEqual(over({ 'a': 1, '1': 2 }), [1, 2]);
+      assert.deepEqual(over({ 'a': 2, '1': 1 }), [2, 1]);
+
+      over = _.over([['a', 1]]);
+
+      assert.deepEqual(over({ 'a': 1 }), [true]);
+      assert.deepEqual(over({ 'a': 2 }), [false]);
+    });
+
+    QUnit.test('should provide arguments to predicates', function(assert) {
+      assert.expect(1);
+
+      var over = _.over(function() {
+        return slice.call(arguments);
+      });
+
+      assert.deepEqual(over('a', 'b', 'c'), [['a', 'b', 'c']]);
+    });
+
+    QUnit.test('should use `this` binding of function for `iteratees`', function(assert) {
+      assert.expect(1);
+
+      var over = _.over(function() { return this.b; }, function() { return this.a; }),
+          object = { 'over': over, 'a': 1, 'b': 2 };
+
+      assert.deepEqual(object.over(), [2, 1]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.overEvery');
+
+  (function() {
+    QUnit.test('should create a function that returns `true` if all predicates return truthy', function(assert) {
+      assert.expect(1);
+
+      var over = _.overEvery(alwaysTrue, alwaysOne, alwaysA);
+      assert.strictEqual(over(), true);
+    });
+
+    QUnit.test('should return `false` as soon as a predicate returns falsey', function(assert) {
+      assert.expect(2);
+
+      var count = 0,
+          countFalse = function() { count++; return false; },
+          countTrue = function() { count++; return true; },
+          over = _.overEvery(countTrue, countFalse, countTrue);
+
+      assert.strictEqual(over(), false);
+      assert.strictEqual(count, 2);
+    });
+
+    QUnit.test('should use `_.identity` when a predicate is nullish', function(assert) {
+      assert.expect(2);
+
+      var over = _.overEvery(undefined, null);
+
+      assert.strictEqual(over(true), true);
+      assert.strictEqual(over(false), false);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overEvery('b', 'a');
+
+      assert.strictEqual(over({ 'a': 1, 'b': 1 }), true);
+      assert.strictEqual(over({ 'a': 0, 'b': 1 }), false);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overEvery({ 'b': 2 }, { 'a': 1 });
+
+      assert.strictEqual(over({ 'a': 1, 'b': 2 }), true);
+      assert.strictEqual(over({ 'a': 0, 'b': 2 }), false);
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overEvery(['b', 2], [['a', 1]]);
+
+      assert.strictEqual(over({ 'a': 1, 'b': 2 }), true);
+      assert.strictEqual(over({ 'a': 0, 'b': 2 }), false);
+    });
+
+    QUnit.test('should differentiate between `_.property` and `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(5);
+
+      var over = _.overEvery(['a', 1]);
+
+      assert.strictEqual(over({ 'a': 1, '1': 1 }), true);
+      assert.strictEqual(over({ 'a': 1, '1': 0 }), false);
+      assert.strictEqual(over({ 'a': 0, '1': 1 }), false);
+
+      over = _.overEvery([['a', 1]]);
+
+      assert.strictEqual(over({ 'a': 1 }), true);
+      assert.strictEqual(over({ 'a': 2 }), false);
+    });
+
+    QUnit.test('should flatten `predicates`', function(assert) {
+      assert.expect(1);
+
+      var over = _.overEvery(alwaysTrue, [alwaysFalse]);
+      assert.strictEqual(over(), false);
+    });
+
+    QUnit.test('should provide arguments to predicates', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      var over = _.overEvery(function() {
+        args = slice.call(arguments);
+      });
+
+      over('a', 'b', 'c');
+      assert.deepEqual(args, ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should use `this` binding of function for `predicates`', function(assert) {
+      assert.expect(2);
+
+      var over = _.overEvery(function() { return this.b; }, function() { return this.a; }),
+          object = { 'over': over, 'a': 1, 'b': 2 };
+
+      assert.strictEqual(object.over(), true);
+
+      object.a = 0;
+      assert.strictEqual(object.over(), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.overSome');
+
+  (function() {
+    QUnit.test('should create a function that returns `true` if any predicates return truthy', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome(alwaysFalse, alwaysOne, alwaysEmptyString);
+      assert.strictEqual(over(), true);
+
+      over = _.overSome(alwaysNull, alwaysA, alwaysZero);
+      assert.strictEqual(over(), true);
+    });
+
+    QUnit.test('should return `true` as soon as `predicate` returns truthy', function(assert) {
+      assert.expect(2);
+
+      var count = 0,
+          countFalse = function() { count++; return false; },
+          countTrue = function() { count++; return true; },
+          over = _.overSome(countFalse, countTrue, countFalse);
+
+      assert.strictEqual(over(), true);
+      assert.strictEqual(count, 2);
+    });
+
+    QUnit.test('should return `false` if all predicates return falsey', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome(alwaysFalse, alwaysFalse, alwaysFalse);
+      assert.strictEqual(over(), false);
+
+      over = _.overSome(alwaysNull, alwaysZero, alwaysEmptyString);
+      assert.strictEqual(over(), false);
+    });
+
+    QUnit.test('should use `_.identity` when a predicate is nullish', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome(undefined, null);
+
+      assert.strictEqual(over(true), true);
+      assert.strictEqual(over(false), false);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome('b', 'a');
+
+      assert.strictEqual(over({ 'a': 1, 'b': 0 }), true);
+      assert.strictEqual(over({ 'a': 0, 'b': 0 }), false);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome({ 'b': 2 }, { 'a': 1 });
+
+      assert.strictEqual(over({ 'a': 0, 'b': 2 }), true);
+      assert.strictEqual(over({ 'a': 0, 'b': 0 }), false);
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome(['a', 1], [['b', 2]]);
+
+      assert.strictEqual(over({ 'a': 0, 'b': 2 }), true);
+      assert.strictEqual(over({ 'a': 0, 'b': 0 }), false);
+    });
+
+    QUnit.test('should differentiate between `_.property` and `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(5);
+
+      var over = _.overSome(['a', 1]);
+
+      assert.strictEqual(over({ 'a': 0, '1': 0 }), false);
+      assert.strictEqual(over({ 'a': 1, '1': 0 }), true);
+      assert.strictEqual(over({ 'a': 0, '1': 1 }), true);
+
+      over = _.overSome([['a', 1]]);
+
+      assert.strictEqual(over({ 'a': 1 }), true);
+      assert.strictEqual(over({ 'a': 2 }), false);
+    });
+
+    QUnit.test('should flatten `predicates`', function(assert) {
+      assert.expect(1);
+
+      var over = _.overSome(alwaysFalse, [alwaysTrue]);
+      assert.strictEqual(over(), true);
+    });
+
+    QUnit.test('should provide arguments to predicates', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      var over = _.overSome(function() {
+        args = slice.call(arguments);
+      });
+
+      over('a', 'b', 'c');
+      assert.deepEqual(args, ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should use `this` binding of function for `predicates`', function(assert) {
+      assert.expect(2);
+
+      var over = _.overSome(function() { return this.b; }, function() { return this.a; }),
+          object = { 'over': over, 'a': 1, 'b': 2 };
+
+      assert.strictEqual(object.over(), true);
+
+      object.a = object.b = 0;
+      assert.strictEqual(object.over(), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.pad');
+
+  (function() {
+    var string = 'abc';
+
+    QUnit.test('should pad a string to a given length', function(assert) {
+      assert.expect(1);
+
+      var values = [, undefined],
+          expected = lodashStable.map(values, lodashStable.constant(' abc  '));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.pad(string, 6, value) : _.pad(string, 6);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should truncate pad characters to fit the pad length', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.pad(string, 8), '  abc   ');
+      assert.strictEqual(_.pad(string, 8, '_-'), '_-abc_-_');
+    });
+
+    QUnit.test('should coerce `string` to a string', function(assert) {
+      assert.expect(1);
+
+      var values = [Object(string), { 'toString': lodashStable.constant(string) }],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.pad(value, 6) === ' abc  ';
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.padEnd');
+
+  (function() {
+    var string = 'abc';
+
+    QUnit.test('should pad a string to a given length', function(assert) {
+      assert.expect(1);
+
+      var values = [, undefined],
+          expected = lodashStable.map(values, lodashStable.constant('abc   '));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.padEnd(string, 6, value) : _.padEnd(string, 6);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should truncate pad characters to fit the pad length', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.padEnd(string, 6, '_-'), 'abc_-_');
+    });
+
+    QUnit.test('should coerce `string` to a string', function(assert) {
+      assert.expect(1);
+
+      var values = [Object(string), { 'toString': lodashStable.constant(string) }],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.padEnd(value, 6) === 'abc   ';
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.padStart');
+
+  (function() {
+    var string = 'abc';
+
+    QUnit.test('should pad a string to a given length', function(assert) {
+      assert.expect(1);
+
+      var values = [, undefined],
+          expected = lodashStable.map(values, lodashStable.constant('   abc'));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.padStart(string, 6, value) : _.padStart(string, 6);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should truncate pad characters to fit the pad length', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.padStart(string, 6, '_-'), '_-_abc');
+    });
+
+    QUnit.test('should coerce `string` to a string', function(assert) {
+      assert.expect(1);
+
+      var values = [Object(string), { 'toString': lodashStable.constant(string) }],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.padStart(value, 6) === '   abc';
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('pad methods');
+
+  lodashStable.each(['pad', 'padStart', 'padEnd'], function(methodName) {
+    var func = _[methodName],
+        isPad = methodName == 'pad',
+        isStart = methodName == 'padStart',
+        string = 'abc';
+
+    QUnit.test('`_.' + methodName + '` should not pad if string is >= `length`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(func(string, 2), string);
+      assert.strictEqual(func(string, 3), string);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat negative `length` as `0`', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([0, -2], function(length) {
+        assert.strictEqual(func(string, length), string);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `length` to a number', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each(['', '4'], function(length) {
+        var actual = length ? (isStart ? ' abc' : 'abc ') : string;
+        assert.strictEqual(func(string, length), actual);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat nullish values as empty strings', function(assert) {
+      assert.expect(6);
+
+      lodashStable.each([undefined, '_-'], function(chars) {
+        var expected = chars ? (isPad ? '__' : chars) : '  ';
+        assert.strictEqual(func(null, 2, chars), expected);
+        assert.strictEqual(func(undefined, 2, chars), expected);
+        assert.strictEqual(func('', 2, chars), expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `string` when `chars` coerces to an empty string', function(assert) {
+      assert.expect(1);
+
+      var values = ['', Object('')],
+          expected = lodashStable.map(values, lodashStable.constant(string));
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.pad(string, 6, value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.parseInt');
+
+  (function() {
+    QUnit.test('should accept a `radix` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.range(2, 37);
+
+      var actual = lodashStable.map(expected, function(radix) {
+        return _.parseInt('10', radix);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should use a radix of `10`, for non-hexadecimals, if `radix` is `undefined` or `0`', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.parseInt('10'), 10);
+      assert.strictEqual(_.parseInt('10', 0), 10);
+      assert.strictEqual(_.parseInt('10', 10), 10);
+      assert.strictEqual(_.parseInt('10', undefined), 10);
+    });
+
+    QUnit.test('should use a radix of `16`, for hexadecimals, if `radix` is `undefined` or `0`', function(assert) {
+      assert.expect(8);
+
+      lodashStable.each(['0x20', '0X20'], function(string) {
+        assert.strictEqual(_.parseInt(string), 32);
+        assert.strictEqual(_.parseInt(string, 0), 32);
+        assert.strictEqual(_.parseInt(string, 16), 32);
+        assert.strictEqual(_.parseInt(string, undefined), 32);
+      });
+    });
+
+    QUnit.test('should use a radix of `10` for string with leading zeros', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.parseInt('08'), 8);
+      assert.strictEqual(_.parseInt('08', 10), 8);
+    });
+
+    QUnit.test('should parse strings with leading whitespace (test in Chrome and Firefox)', function(assert) {
+      assert.expect(2);
+
+      var expected = [8, 8, 10, 10, 32, 32, 32, 32];
+
+      lodashStable.times(2, function(index) {
+        var actual = [],
+            func = (index ? (lodashBizarro || {}) : _).parseInt;
+
+        if (func) {
+          lodashStable.times(2, function(otherIndex) {
+            var string = otherIndex ? '10' : '08';
+            actual.push(
+              func(whitespace + string, 10),
+              func(whitespace + string)
+            );
+          });
+
+          lodashStable.each(['0x20', '0X20'], function(string) {
+            actual.push(
+              func(whitespace + string),
+              func(whitespace + string, 16)
+            );
+          });
+
+          assert.deepEqual(actual, expected);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    QUnit.test('should coerce `radix` to a number', function(assert) {
+      assert.expect(2);
+
+      var object = { 'valueOf': alwaysZero };
+      assert.strictEqual(_.parseInt('08', object), 8);
+      assert.strictEqual(_.parseInt('0x20', object), 32);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(2);
+
+      var strings = lodashStable.map(['6', '08', '10'], Object),
+          actual = lodashStable.map(strings, _.parseInt);
+
+      assert.deepEqual(actual, [6, 8, 10]);
+
+      actual = lodashStable.map('123', _.parseInt);
+      assert.deepEqual(actual, [1, 2, 3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('partial methods');
+
+  lodashStable.each(['partial', 'partialRight'], function(methodName) {
+    var func = _[methodName],
+        isPartial = methodName == 'partial',
+        ph = func.placeholder;
+
+    QUnit.test('`_.' + methodName + '` partially applies arguments', function(assert) {
+      assert.expect(1);
+
+      var par = func(identity, 'a');
+      assert.strictEqual(par(), 'a');
+    });
+
+    QUnit.test('`_.' + methodName + '` creates a function that can be invoked with additional arguments', function(assert) {
+      assert.expect(1);
+
+      var fn = function(a, b) { return [a, b]; },
+          par = func(fn, 'a'),
+          expected = isPartial ? ['a', 'b'] : ['b', 'a'];
+
+      assert.deepEqual(par('b'), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` works when there are no partially applied arguments and the created function is invoked without additional arguments', function(assert) {
+      assert.expect(1);
+
+      var fn = function() { return arguments.length; },
+          par = func(fn);
+
+      assert.strictEqual(par(), 0);
+    });
+
+    QUnit.test('`_.' + methodName + '` works when there are no partially applied arguments and the created function is invoked with additional arguments', function(assert) {
+      assert.expect(1);
+
+      var par = func(identity);
+      assert.strictEqual(par('a'), 'a');
+    });
+
+    QUnit.test('`_.' + methodName + '` should support placeholders', function(assert) {
+      assert.expect(4);
+
+      var fn = function() { return slice.call(arguments); },
+          par = func(fn, ph, 'b', ph);
+
+      assert.deepEqual(par('a', 'c'), ['a', 'b', 'c']);
+      assert.deepEqual(par('a'), ['a', 'b', undefined]);
+      assert.deepEqual(par(), [undefined, 'b', undefined]);
+
+      if (isPartial) {
+        assert.deepEqual(par('a', 'c', 'd'), ['a', 'b', 'c', 'd']);
+      } else {
+        par = func(fn, ph, 'c', ph);
+        assert.deepEqual(par('a', 'b', 'd'), ['a', 'b', 'c', 'd']);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should use `_.placeholder` when set', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var _ph = _.placeholder = {},
+            fn = function() { return slice.call(arguments); },
+            par = func(fn, _ph, 'b', ph),
+            expected = isPartial ? ['a', 'b', ph, 'c'] : ['a', 'c', 'b', ph];
+
+        assert.deepEqual(par('a', 'c'), expected);
+        delete _.placeholder;
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` creates a function with a `length` of `0`', function(assert) {
+      assert.expect(1);
+
+      var fn = function(a, b, c) {},
+          par = func(fn, 'a');
+
+      assert.strictEqual(par.length, 0);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ensure `new par` is an instance of `func`', function(assert) {
+      assert.expect(2);
+
+      function Foo(value) {
+        return value && object;
+      }
+
+      var object = {},
+          par = func(Foo);
+
+      assert.ok(new par instanceof Foo);
+      assert.strictEqual(new par(true), object);
+    });
+
+    QUnit.test('`_.' + methodName + '` should clone metadata for created functions', function(assert) {
+      assert.expect(3);
+
+      function greet(greeting, name) {
+        return greeting + ' ' + name;
+      }
+
+      var par1 = func(greet, 'hi'),
+          par2 = func(par1, 'barney'),
+          par3 = func(par1, 'pebbles');
+
+      assert.strictEqual(par1('fred'), isPartial ? 'hi fred' : 'fred hi');
+      assert.strictEqual(par2(), isPartial ? 'hi barney'  : 'barney hi');
+      assert.strictEqual(par3(), isPartial ? 'hi pebbles' : 'pebbles hi');
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with curried functions', function(assert) {
+      assert.expect(2);
+
+      var fn = function(a, b, c) { return a + b + c; },
+          curried = _.curry(func(fn, 1), 2);
+
+      assert.strictEqual(curried(2, 3), 6);
+      assert.strictEqual(curried(2)(3), 6);
+    });
+
+    QUnit.test('should work with placeholders and curried functions', function(assert) {
+      assert.expect(1);
+
+      var fn = function() { return slice.call(arguments); },
+          curried = _.curry(fn),
+          par = func(curried, ph, 'b', ph, 'd');
+
+      assert.deepEqual(par('a', 'c'), ['a', 'b', 'c', 'd']);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.partialRight');
+
+  (function() {
+    QUnit.test('should work as a deep `_.defaults`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': { 'b': 1 } },
+          source = { 'a': { 'b': 2, 'c': 3 } },
+          expected = { 'a': { 'b': 1, 'c': 3 } };
+
+      var defaultsDeep = _.partialRight(_.mergeWith, function deep(value, other) {
+        return lodashStable.isObject(value) ? _.mergeWith(value, other, deep) : value;
+      });
+
+      assert.deepEqual(defaultsDeep(object, source), expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('methods using `createWrapper`');
+
+  (function() {
+    function fn() {
+      return slice.call(arguments);
+    }
+
+    var ph1 = _.bind.placeholder,
+        ph2 = _.bindKey.placeholder,
+        ph3 = _.partial.placeholder,
+        ph4 = _.partialRight.placeholder;
+
+    QUnit.test('should work with combinations of partial functions', function(assert) {
+      assert.expect(1);
+
+      var a = _.partial(fn),
+          b = _.partialRight(a, 3),
+          c = _.partial(b, 1);
+
+      assert.deepEqual(c(2), [1, 2, 3]);
+    });
+
+    QUnit.test('should work with combinations of bound and partial functions', function(assert) {
+      assert.expect(3);
+
+      var fn = function() {
+        var result = [this.a];
+        push.apply(result, arguments);
+        return result;
+      };
+
+      var expected = [1, 2, 3, 4],
+          object = { 'a': 1, 'fn': fn };
+
+      var a = _.bindKey(object, 'fn'),
+          b = _.partialRight(a, 4),
+          c = _.partial(b, 2);
+
+      assert.deepEqual(c(3), expected);
+
+      a = _.bind(fn, object);
+      b = _.partialRight(a, 4);
+      c = _.partial(b, 2);
+
+      assert.deepEqual(c(3), expected);
+
+      a = _.partial(fn, 2);
+      b = _.bind(a, object);
+      c = _.partialRight(b, 4);
+
+      assert.deepEqual(c(3), expected);
+    });
+
+    QUnit.test('should ensure `new combo` is an instance of `func`', function(assert) {
+      assert.expect(2);
+
+      function Foo(a, b, c) {
+        return b === 0 && object;
+      }
+
+      var combo = _.partial(_.partialRight(Foo, 3), 1),
+          object = {};
+
+      assert.ok(new combo(2) instanceof Foo);
+      assert.strictEqual(new combo(0), object);
+    });
+
+    QUnit.test('should work with combinations of functions with placeholders', function(assert) {
+      assert.expect(3);
+
+      var expected = [1, 2, 3, 4, 5, 6],
+          object = { 'fn': fn };
+
+      var a = _.bindKey(object, 'fn', ph2, 2),
+          b = _.partialRight(a, ph4, 6),
+          c = _.partial(b, 1, ph3, 4);
+
+      assert.deepEqual(c(3, 5), expected);
+
+      a = _.bind(fn, object, ph1, 2);
+      b = _.partialRight(a, ph4, 6);
+      c = _.partial(b, 1, ph3, 4);
+
+      assert.deepEqual(c(3, 5), expected);
+
+      a = _.partial(fn, ph3, 2);
+      b = _.bind(a, object, 1, ph1, 4);
+      c = _.partialRight(b, ph4, 6);
+
+      assert.deepEqual(c(3, 5), expected);
+    });
+
+    QUnit.test('should work with combinations of functions with overlapping placeholders', function(assert) {
+      assert.expect(3);
+
+      var expected = [1, 2, 3, 4],
+          object = { 'fn': fn };
+
+      var a = _.bindKey(object, 'fn', ph2, 2),
+          b = _.partialRight(a, ph4, 4),
+          c = _.partial(b, ph3, 3);
+
+      assert.deepEqual(c(1), expected);
+
+      a = _.bind(fn, object, ph1, 2);
+      b = _.partialRight(a, ph4, 4);
+      c = _.partial(b, ph3, 3);
+
+      assert.deepEqual(c(1), expected);
+
+      a = _.partial(fn, ph3, 2);
+      b = _.bind(a, object, ph1, 3);
+      c = _.partialRight(b, ph4, 4);
+
+      assert.deepEqual(c(1), expected);
+    });
+
+    QUnit.test('should work with recursively bound functions', function(assert) {
+      assert.expect(1);
+
+      var fn = function() {
+        return this.a;
+      };
+
+      var a = _.bind(fn, { 'a': 1 }),
+          b = _.bind(a,  { 'a': 2 }),
+          c = _.bind(b,  { 'a': 3 });
+
+      assert.strictEqual(c(), 1);
+    });
+
+    QUnit.test('should work when hot', function(assert) {
+      assert.expect(12);
+
+      lodashStable.times(2, function(index) {
+        var fn = function() {
+          var result = [this];
+          push.apply(result, arguments);
+          return result;
+        };
+
+        var object = {},
+            bound1 = index ? _.bind(fn, object, 1) : _.bind(fn, object),
+            expected = [object, 1, 2, 3];
+
+        var actual = _.last(lodashStable.times(HOT_COUNT, function() {
+          var bound2 = index ? _.bind(bound1, null, 2) : _.bind(bound1);
+          return index ? bound2(3) : bound2(1, 2, 3);
+        }));
+
+        assert.deepEqual(actual, expected);
+
+        actual = _.last(lodashStable.times(HOT_COUNT, function() {
+          var bound1 = index ? _.bind(fn, object, 1) : _.bind(fn, object),
+              bound2 = index ? _.bind(bound1, null, 2) : _.bind(bound1);
+
+          return index ? bound2(3) : bound2(1, 2, 3);
+        }));
+
+        assert.deepEqual(actual, expected);
+      });
+
+      lodashStable.each(['curry', 'curryRight'], function(methodName, index) {
+        var fn = function(a, b, c) { return [a, b, c]; },
+            curried = _[methodName](fn),
+            expected = index ? [3, 2, 1] :  [1, 2, 3];
+
+        var actual = _.last(lodashStable.times(HOT_COUNT, function() {
+          return curried(1)(2)(3);
+        }));
+
+        assert.deepEqual(actual, expected);
+
+        actual = _.last(lodashStable.times(HOT_COUNT, function() {
+          var curried = _[methodName](fn);
+          return curried(1)(2)(3);
+        }));
+
+        assert.deepEqual(actual, expected);
+      });
+
+      lodashStable.each(['partial', 'partialRight'], function(methodName, index) {
+        var func = _[methodName],
+            fn = function() { return slice.call(arguments); },
+            par1 = func(fn, 1),
+            expected = index ? [3, 2, 1] : [1, 2, 3];
+
+        var actual = _.last(lodashStable.times(HOT_COUNT, function() {
+          var par2 = func(par1, 2);
+          return par2(3);
+        }));
+
+        assert.deepEqual(actual, expected);
+
+        actual = _.last(lodashStable.times(HOT_COUNT, function() {
+          var par1 = func(fn, 1),
+              par2 = func(par1, 2);
+
+          return par2(3);
+        }));
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.partition');
+
+  (function() {
+    var array = [1, 0, 1];
+
+    QUnit.test('should split elements into two groups by `predicate`', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(_.partition([], identity), [[], []]);
+      assert.deepEqual(_.partition(array, alwaysTrue), [array, []]);
+      assert.deepEqual(_.partition(array, alwaysFalse), [[], array]);
+    });
+
+    QUnit.test('should use `_.identity` when `predicate` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([[1, 1], [0]]));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.partition(array, value) : _.partition(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': 1 }, { 'a': 1 }, { 'b': 2 }],
+          actual = _.partition(objects, 'a');
+
+      assert.deepEqual(actual, [objects.slice(0, 2), objects.slice(2)]);
+    });
+
+    QUnit.test('should work with a number for `predicate`', function(assert) {
+      assert.expect(2);
+
+      var array = [
+        [1, 0],
+        [0, 1],
+        [1, 0]
+      ];
+
+      assert.deepEqual(_.partition(array, 0), [[array[0], array[2]], [array[1]]]);
+      assert.deepEqual(_.partition(array, 1), [[array[1]], [array[0], array[2]]]);
+    });
+
+    QUnit.test('should work with an object for `collection`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.partition({ 'a': 1.1, 'b': 0.2, 'c': 1.3 }, Math.floor);
+      assert.deepEqual(actual, [[1.1, 1.3], [0.2]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.pick');
+
+  (function() {
+    var args = arguments,
+        object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 };
+
+    QUnit.test('should flatten `props`', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.pick(object, 'a', 'c'), { 'a': 1, 'c': 3 });
+      assert.deepEqual(_.pick(object, ['a', 'd'], 'c'), { 'a': 1, 'c': 3, 'd': 4 });
+    });
+
+    QUnit.test('should work with a primitive `object` argument', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.pick('', 'slice'), { 'slice': ''.slice });
+    });
+
+    QUnit.test('should return an empty object when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([null, undefined], function(value) {
+        assert.deepEqual(_.pick(value, 'valueOf'), {});
+      });
+    });
+
+    QUnit.test('should work with `arguments` objects as secondary arguments', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.pick(object, args), { 'a': 1, 'c': 3 });
+    });
+
+    QUnit.test('should coerce property names to strings', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.pick({ '0': 'a', '1': 'b' }, 0), { '0': 'a' });
+    });
+  }('a', 'c'));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.pickBy');
+
+  (function() {
+    QUnit.test('should work with a predicate argument', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 };
+
+      var actual = _.pickBy(object, function(n) {
+        return n == 1 || n == 3;
+      });
+
+      assert.deepEqual(actual, { 'a': 1, 'c': 3 });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('pick methods');
+
+  lodashStable.each(['pick', 'pickBy'], function(methodName) {
+    var expected = { 'a': 1, 'c': 3 },
+        func = _[methodName],
+        object = { 'a': 1, 'b': 2, 'c': 3, 'd': 4 },
+        prop = lodashStable.nthArg(1);
+
+    if (methodName == 'pickBy') {
+      prop = function(object, props) {
+        props = lodashStable.castArray(props);
+        return function(value) {
+          return lodashStable.some(props, function(key) {
+            key = lodashStable.isSymbol(key) ? key : lodashStable.toString(key);
+            return object[key] === value;
+          });
+        };
+      };
+    }
+    QUnit.test('`_.' + methodName + '` should create an object of picked string keyed properties', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(func(object, prop(object, 'a')), { 'a': 1 });
+      assert.deepEqual(func(object, prop(object, ['a', 'c'])), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should pick inherited string keyed properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {}
+      Foo.prototype = object;
+
+      var foo = new Foo;
+      assert.deepEqual(func(foo, prop(foo, ['a', 'c'])), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': 'a', '0': 'b' },
+          props = [-0, Object(-0), 0, Object(0)],
+          expected = [{ '-0': 'a' }, { '-0': 'a' }, { '0': 'b' }, { '0': 'b' }];
+
+      var actual = lodashStable.map(props, function(key) {
+        return func(object, prop(object, key));
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should pick symbol properties', function(assert) {
+      assert.expect(2);
+
+      function Foo() {
+        this[symbol] = 1;
+      }
+
+      if (Symbol) {
+        var symbol2 = Symbol('b');
+        Foo.prototype[symbol2] = 2;
+
+        var foo = new Foo,
+            actual = func(foo, prop(foo, [symbol, symbol2]));
+
+        assert.strictEqual(actual[symbol], 1);
+        assert.strictEqual(actual[symbol2], 2);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with an array `object` argument', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      assert.deepEqual(func(array, prop(array, '1')), { '1': 2 });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.property');
+
+  (function() {
+    QUnit.test('should create a function that plucks a property value of a given object', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': 1 };
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var prop = _.property(path);
+        assert.strictEqual(prop.length, 1);
+        assert.strictEqual(prop(object), 1);
+      });
+    });
+
+    QUnit.test('should pluck deep property values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var prop = _.property(path);
+        assert.strictEqual(prop(object), 2);
+      });
+    });
+
+    QUnit.test('should pluck inherited property values', function(assert) {
+      assert.expect(2);
+
+      function Foo() {}
+      Foo.prototype.a = 1;
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var prop = _.property(path);
+        assert.strictEqual(prop(new Foo), 1);
+      });
+    });
+
+    QUnit.test('should work with a non-string `path`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3];
+
+      lodashStable.each([1, [1]], function(path) {
+        var prop = _.property(path);
+        assert.strictEqual(prop(array), 2);
+      });
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': 'a', '0': 'b' },
+          props = [-0, Object(-0), 0, Object(0)];
+
+      var actual = lodashStable.map(props, function(key) {
+        var prop = _.property(key);
+        return prop(object);
+      });
+
+      assert.deepEqual(actual, ['a', 'a', 'b', 'b']);
+    });
+
+    QUnit.test('should coerce key to a string', function(assert) {
+      assert.expect(1);
+
+      function fn() {}
+      fn.toString = lodashStable.constant('fn');
+
+      var objects = [{ 'null': 1 }, { 'undefined': 2 }, { 'fn': 3 }, { '[object Object]': 4 }],
+          values = [null, undefined, fn, {}];
+
+      var actual = lodashStable.transform(objects, function(result, object, index) {
+        var key = values[index];
+        lodashStable.each([key, [key]], function(path) {
+          var prop = _.property(key);
+          result.push(prop(object));
+        });
+      });
+
+      assert.deepEqual(actual, [1, 1, 2, 2, 3, 3, 4, 4]);
+    });
+
+    QUnit.test('should pluck a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': 1, 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        var prop = _.property(path);
+        assert.strictEqual(prop(object), 1);
+      });
+    });
+
+    QUnit.test('should return `undefined` when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        var prop = _.property(path);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? prop(value) : prop();
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` with deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) {
+        var prop = _.property(path);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? prop(value) : prop();
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` if parts of `path` are missing', function(assert) {
+      assert.expect(4);
+
+      var object = {};
+
+      lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) {
+        var prop = _.property(path);
+        assert.strictEqual(prop(object), undefined);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.propertyOf');
+
+  (function() {
+    QUnit.test('should create a function that plucks a property value of a given key', function(assert) {
+      assert.expect(3);
+
+      var object = { 'a': 1 },
+          propOf = _.propertyOf(object);
+
+      assert.strictEqual(propOf.length, 1);
+      lodashStable.each(['a', ['a']], function(path) {
+        assert.strictEqual(propOf(path), 1);
+      });
+    });
+
+    QUnit.test('should pluck deep property values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': 2 } },
+          propOf = _.propertyOf(object);
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(propOf(path), 2);
+      });
+    });
+
+    QUnit.test('should pluck inherited property values', function(assert) {
+      assert.expect(2);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var propOf = _.propertyOf(new Foo);
+
+      lodashStable.each(['b', ['b']], function(path) {
+        assert.strictEqual(propOf(path), 2);
+      });
+    });
+
+    QUnit.test('should work with a non-string `path`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          propOf = _.propertyOf(array);
+
+      lodashStable.each([1, [1]], function(path) {
+        assert.strictEqual(propOf(path), 2);
+      });
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': 'a', '0': 'b' },
+          props = [-0, Object(-0), 0, Object(0)];
+
+      var actual = lodashStable.map(props, function(key) {
+        var propOf = _.propertyOf(object);
+        return propOf(key);
+      });
+
+      assert.deepEqual(actual, ['a', 'a', 'b', 'b']);
+    });
+
+    QUnit.test('should coerce key to a string', function(assert) {
+      assert.expect(1);
+
+      function fn() {}
+      fn.toString = lodashStable.constant('fn');
+
+      var objects = [{ 'null': 1 }, { 'undefined': 2 }, { 'fn': 3 }, { '[object Object]': 4 }],
+          values = [null, undefined, fn, {}];
+
+      var actual = lodashStable.transform(objects, function(result, object, index) {
+        var key = values[index];
+        lodashStable.each([key, [key]], function(path) {
+          var propOf = _.propertyOf(object);
+          result.push(propOf(key));
+        });
+      });
+
+      assert.deepEqual(actual, [1, 1, 2, 2, 3, 3, 4, 4]);
+    });
+
+    QUnit.test('should pluck a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': 1, 'a': { 'b': 2 } },
+          propOf = _.propertyOf(object);
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        assert.strictEqual(propOf(path), 1);
+      });
+    });
+
+    QUnit.test('should return `undefined` when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        var actual = lodashStable.map(values, function(value, index) {
+          var propOf = index ? _.propertyOf(value) : _.propertyOf();
+          return propOf(path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` with deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, noop);
+
+      lodashStable.each(['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']], function(path) {
+        var actual = lodashStable.map(values, function(value, index) {
+          var propOf = index ? _.propertyOf(value) : _.propertyOf();
+          return propOf(path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should return `undefined` if parts of `path` are missing', function(assert) {
+      assert.expect(4);
+
+      var propOf = _.propertyOf({});
+
+      lodashStable.each(['a', 'a[1].b.c', ['a'], ['a', '1', 'b', 'c']], function(path) {
+        assert.strictEqual(propOf(path), undefined);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.pullAllBy');
+
+  (function() {
+    QUnit.test('should accept an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+
+      var actual = _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], function(object) {
+        return object.x;
+      });
+
+      assert.deepEqual(actual, [{ 'x': 2 }]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args,
+          array = [{ 'x': 1 }, { 'x': 2 }, { 'x': 3 }, { 'x': 1 }];
+
+      _.pullAllBy(array, [{ 'x': 1 }, { 'x': 3 }], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [{ 'x': 1 }]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.pullAllWith');
+
+  (function() {
+    QUnit.test('should work with a `comparator` argument', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 1, 'y': 1 }, { 'x': 2, 'y': 2 }, { 'x': 3, 'y': 3 }],
+          expected = [objects[0], objects[2]],
+          actual = _.pullAllWith(objects, [{ 'x': 2, 'y': 2 }], lodashStable.isEqual);
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('pull methods');
+
+  lodashStable.each(['pull', 'pullAll', 'pullAllWith'], function(methodName) {
+    var func = _[methodName],
+        isPull = methodName == 'pull';
+
+    function pull(array, values) {
+      return isPull
+        ? func.apply(undefined, [array].concat(values))
+        : func(array, values);
+    }
+
+    QUnit.test('`_.' + methodName + '` should modify and return the array', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          actual = pull(array, [1, 3]);
+
+      assert.deepEqual(array, [2]);
+      assert.ok(actual === array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve holes in arrays', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4];
+      delete array[1];
+      delete array[3];
+
+      pull(array, [1]);
+      assert.notOk('0' in array);
+      assert.notOk('2' in array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat holes as `undefined`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      delete array[1];
+
+      pull(array, [undefined]);
+      assert.deepEqual(array, [1, 3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should match `NaN`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, NaN, 3, NaN];
+
+      pull(array, [NaN]);
+      assert.deepEqual(array, [1, 3]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.pullAt');
+
+  (function() {
+    QUnit.test('should modify the array and return removed elements', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          actual = _.pullAt(array, [0, 1]);
+
+      assert.deepEqual(array, [3]);
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('should work with unsorted indexes', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],
+          actual = _.pullAt(array, [1, 3, 11, 7, 5, 9]);
+
+      assert.deepEqual(array, [1, 3, 5, 7, 9, 11]);
+      assert.deepEqual(actual, [2, 4, 12, 8, 6, 10]);
+    });
+
+    QUnit.test('should work with repeated indexes', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4],
+          actual = _.pullAt(array, [0, 2, 0, 1, 0, 2]);
+
+      assert.deepEqual(array, [4]);
+      assert.deepEqual(actual, [1, 3, 1, 2, 1, 3]);
+    });
+
+    QUnit.test('should use `undefined` for nonexistent indexes', function(assert) {
+      assert.expect(2);
+
+      var array = ['a', 'b', 'c'],
+          actual = _.pullAt(array, [2, 4, 0]);
+
+      assert.deepEqual(array, ['b']);
+      assert.deepEqual(actual, ['c', undefined, 'a']);
+    });
+
+    QUnit.test('should flatten `indexes`', function(assert) {
+      assert.expect(4);
+
+      var array = ['a', 'b', 'c'];
+      assert.deepEqual(_.pullAt(array, 2, 0), ['c', 'a']);
+      assert.deepEqual(array, ['b']);
+
+      array = ['a', 'b', 'c', 'd'];
+      assert.deepEqual(_.pullAt(array, [3, 0], 2), ['d', 'a', 'c']);
+      assert.deepEqual(array, ['b']);
+    });
+
+    QUnit.test('should return an empty array when no indexes are given', function(assert) {
+      assert.expect(4);
+
+      var array = ['a', 'b', 'c'],
+          actual = _.pullAt(array);
+
+      assert.deepEqual(array, ['a', 'b', 'c']);
+      assert.deepEqual(actual, []);
+
+      actual = _.pullAt(array, [], []);
+
+      assert.deepEqual(array, ['a', 'b', 'c']);
+      assert.deepEqual(actual, []);
+    });
+
+    QUnit.test('should work with non-index paths', function(assert) {
+      assert.expect(2);
+
+      var values = lodashStable.reject(empties, function(value) {
+        return (value === 0) || lodashStable.isArray(value);
+      }).concat(-1, 1.1);
+
+      var array = lodashStable.transform(values, function(result, value) {
+        result[value] = 1;
+      }, []);
+
+      var expected = lodashStable.map(values, alwaysOne),
+          actual = _.pullAt(array, values);
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(values, noop),
+      actual = lodashStable.at(array, values);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var props = [-0, Object(-0), 0, Object(0)];
+
+      var actual = lodashStable.map(props, function(key) {
+        var array = [-1];
+        array['-0'] = -2;
+        return _.pullAt(array, key);
+      });
+
+      assert.deepEqual(actual, [[-2], [-2], [-1], [-1]]);
+    });
+
+    QUnit.test('should work with deep paths', function(assert) {
+      assert.expect(3);
+
+      var array = [];
+      array.a = { 'b': 2 };
+
+      var actual = _.pullAt(array, 'a.b');
+
+      assert.deepEqual(actual, [2]);
+      assert.deepEqual(array.a, {});
+
+      try {
+        actual = _.pullAt(array, 'a.b.c');
+      } catch (e) {}
+
+      assert.deepEqual(actual, [undefined]);
+    });
+
+    QUnit.test('should work with a falsey `array` argument when keys are given', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.slice(),
+          expected = lodashStable.map(values, lodashStable.constant(Array(4)));
+
+      var actual = lodashStable.map(values, function(array) {
+        try {
+          return _.pullAt(array, 0, 1, 'pop', 'push');
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.random');
+
+  (function() {
+    var array = Array(1000);
+
+    QUnit.test('should return `0` or `1` when no arguments are given', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.uniq(lodashStable.map(array, function() {
+        return _.random();
+      })).sort();
+
+      assert.deepEqual(actual, [0, 1]);
+    });
+
+    QUnit.test('should support a `min` and `max` argument', function(assert) {
+      assert.expect(1);
+
+      var min = 5,
+          max = 10;
+
+      assert.ok(_.some(array, function() {
+        var result = _.random(min, max);
+        return result >= min && result <= max;
+      }));
+    });
+
+    QUnit.test('should support not providing a `max` argument', function(assert) {
+      assert.expect(1);
+
+      var min = 0,
+          max = 5;
+
+      assert.ok(_.some(array, function() {
+        var result = _.random(max);
+        return result >= min && result <= max;
+      }));
+    });
+
+    QUnit.test('should swap `min` and `max` when `min` > `max`', function(assert) {
+      assert.expect(1);
+
+      var min = 4,
+          max = 2,
+          expected = [2, 3, 4];
+
+      var actual = lodashStable.uniq(lodashStable.map(array, function() {
+        return _.random(min, max);
+      })).sort();
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should support large integer values', function(assert) {
+      assert.expect(2);
+
+      var min = Math.pow(2, 31),
+          max = Math.pow(2, 62);
+
+      assert.ok(lodashStable.every(array, function() {
+        var result = _.random(min, max);
+        return result >= min && result <= max;
+      }));
+
+      assert.ok(_.some(array, function() {
+        return _.random(MAX_INTEGER) > 0;
+      }));
+    });
+
+    QUnit.test('should coerce arguments to finite numbers', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.random('1', '1'), 1);
+      assert.strictEqual(_.random(NaN, NaN), 0);
+    });
+
+    QUnit.test('should support floats', function(assert) {
+      assert.expect(2);
+
+      var min = 1.5,
+          max = 1.6,
+          actual = _.random(min, max);
+
+      assert.ok(actual % 1);
+      assert.ok(actual >= min && actual <= max);
+    });
+
+    QUnit.test('should support providing a `floating` argument', function(assert) {
+      assert.expect(3);
+
+      var actual = _.random(true);
+      assert.ok(actual % 1 && actual >= 0 && actual <= 1);
+
+      actual = _.random(2, true);
+      assert.ok(actual % 1 && actual >= 0 && actual <= 2);
+
+      actual = _.random(2, 4, true);
+      assert.ok(actual % 1 && actual >= 2 && actual <= 4);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3],
+          expected = lodashStable.map(array, alwaysTrue),
+          randoms = lodashStable.map(array, _.random);
+
+      var actual = lodashStable.map(randoms, function(result, index) {
+        return result >= 0 && result <= array[index] && (result % 1) == 0;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('range methods');
+
+  lodashStable.each(['range', 'rangeRight'], function(methodName) {
+    var func = _[methodName],
+        isRange = methodName == 'range';
+
+    function resolve(range) {
+      return isRange ? range : range.reverse();
+    }
+
+    QUnit.test('`_.' + methodName + '` should infer the sign of `step` when only `end` is given', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(func(4), resolve([0, 1, 2, 3]));
+      assert.deepEqual(func(-4), resolve([0, -1, -2, -3]));
+    });
+
+    QUnit.test('`_.' + methodName + '` should infer the sign of `step` when only `start` and `end` are given', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(func(1, 5), resolve([1, 2, 3, 4]));
+      assert.deepEqual(func(5, 1), resolve([5, 4, 3, 2]));
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `start`, `end`, and `step` arguments', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(func(0, -4, -1), resolve([0, -1, -2, -3]));
+      assert.deepEqual(func(5, 1, -1), resolve([5, 4, 3, 2]));
+      assert.deepEqual(func(0, 20, 5), resolve([0, 5, 10, 15]));
+    });
+
+    QUnit.test('`_.' + methodName + '` should support a `step` of `0`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(1, 4, 0), [1, 1, 1]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a `step` larger than `end`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(1, 5, 20), [1]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a negative `step`', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(func(0, -4, -1), resolve([0, -1, -2, -3]));
+      assert.deepEqual(func(21, 10, -3), resolve([21, 18, 15, 12]));
+    });
+
+    QUnit.test('`_.' + methodName + '` should support `start` of `-0`', function(assert) {
+      assert.expect(1);
+
+      var actual = func(-0, 1);
+      assert.strictEqual(1 / actual[0], -Infinity);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat falsey `start` arguments as `0`', function(assert) {
+      assert.expect(13);
+
+      lodashStable.each(falsey, function(value, index) {
+        if (index) {
+          assert.deepEqual(func(value), []);
+          assert.deepEqual(func(value, 1), [0]);
+        } else {
+          assert.deepEqual(func(), []);
+        }
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce arguments to finite numbers', function(assert) {
+      assert.expect(1);
+
+      var actual = [func('0', 1), func('1'), func(0, 1, '1'), func(NaN), func(NaN, NaN)];
+      assert.deepEqual(actual, [[0], [0], [0], [], []]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          object = { 'a': 1, 'b': 2, 'c': 3 },
+          expected = lodashStable.map([[0], [0, 1], [0, 1, 2]], resolve);
+
+      lodashStable.each([array, object], function(collection) {
+        var actual = lodashStable.map(collection, func);
+        assert.deepEqual(actual, expected);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.rearg');
+
+  (function() {
+    function fn() {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should reorder arguments provided to `func`', function(assert) {
+      assert.expect(1);
+
+      var rearged = _.rearg(fn, [2, 0, 1]);
+      assert.deepEqual(rearged('b', 'c', 'a'), ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should work with repeated indexes', function(assert) {
+      assert.expect(1);
+
+      var rearged = _.rearg(fn, [1, 1, 1]);
+      assert.deepEqual(rearged('c', 'a', 'b'), ['a', 'a', 'a']);
+    });
+
+    QUnit.test('should use `undefined` for nonexistent indexes', function(assert) {
+      assert.expect(1);
+
+      var rearged = _.rearg(fn, [1, 4]);
+      assert.deepEqual(rearged('b', 'a', 'c'), ['a', undefined, 'c']);
+    });
+
+    QUnit.test('should use `undefined` for non-index values', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.reject(empties, function(value) {
+        return (value === 0) || lodashStable.isArray(value);
+      }).concat(-1, 1.1);
+
+      var expected = lodashStable.map(values, lodashStable.constant([undefined, 'b', 'c']));
+
+      var actual = lodashStable.map(values, function(value) {
+        var rearged = _.rearg(fn, [value]);
+        return rearged('a', 'b', 'c');
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not rearrange arguments when no indexes are given', function(assert) {
+      assert.expect(2);
+
+      var rearged = _.rearg(fn);
+      assert.deepEqual(rearged('a', 'b', 'c'), ['a', 'b', 'c']);
+
+      rearged = _.rearg(fn, [], []);
+      assert.deepEqual(rearged('a', 'b', 'c'), ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should accept multiple index arguments', function(assert) {
+      assert.expect(1);
+
+      var rearged = _.rearg(fn, 2, 0, 1);
+      assert.deepEqual(rearged('b', 'c', 'a'), ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should accept multiple arrays of indexes', function(assert) {
+      assert.expect(1);
+
+      var rearged = _.rearg(fn, [2], [0, 1]);
+      assert.deepEqual(rearged('b', 'c', 'a'), ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should work with fewer indexes than arguments', function(assert) {
+      assert.expect(1);
+
+      var rearged = _.rearg(fn, [1, 0]);
+      assert.deepEqual(rearged('b', 'a', 'c'), ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should work on functions that have been rearged', function(assert) {
+      assert.expect(1);
+
+      var rearged1 = _.rearg(fn, 2, 1, 0),
+          rearged2 = _.rearg(rearged1, 1, 0, 2);
+
+      assert.deepEqual(rearged2('b', 'c', 'a'), ['a', 'b', 'c']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.reduce');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should use the first element of a collection as the default `accumulator`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.reduce(array), 1);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments when iterating an array', function(assert) {
+      assert.expect(2);
+
+      var args;
+
+      _.reduce(array, function() {
+        args || (args = slice.call(arguments));
+      }, 0);
+
+      assert.deepEqual(args, [0, 1, 0, array]);
+
+      args = undefined;
+      _.reduce(array, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [1, 2, 1, array]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments when iterating an object', function(assert) {
+      assert.expect(2);
+
+      var args,
+          object = { 'a': 1, 'b': 2 },
+          firstKey = _.head(_.keys(object));
+
+      var expected = firstKey == 'a'
+        ? [0, 1, 'a', object]
+        : [0, 2, 'b', object];
+
+      _.reduce(object, function() {
+        args || (args = slice.call(arguments));
+      }, 0);
+
+      assert.deepEqual(args, expected);
+
+      args = undefined;
+      expected = firstKey == 'a'
+        ? [1, 2, 'b', object]
+        : [2, 1, 'a', object];
+
+      _.reduce(object, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.reduceRight');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should use the last element of a collection as the default `accumulator`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.reduceRight(array), 3);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments when iterating an array', function(assert) {
+      assert.expect(2);
+
+      var args;
+
+      _.reduceRight(array, function() {
+        args || (args = slice.call(arguments));
+      }, 0);
+
+      assert.deepEqual(args, [0, 3, 2, array]);
+
+      args = undefined;
+      _.reduceRight(array, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [3, 2, 1, array]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments when iterating an object', function(assert) {
+      assert.expect(2);
+
+      var args,
+          object = { 'a': 1, 'b': 2 },
+          isFIFO = lodashStable.keys(object)[0] == 'a';
+
+      var expected = isFIFO
+        ? [0, 2, 'b', object]
+        : [0, 1, 'a', object];
+
+      _.reduceRight(object, function() {
+        args || (args = slice.call(arguments));
+      }, 0);
+
+      assert.deepEqual(args, expected);
+
+      args = undefined;
+      expected = isFIFO
+        ? [2, 1, 'a', object]
+        : [1, 2, 'b', object];
+
+      _.reduceRight(object, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('reduce methods');
+
+  lodashStable.each(['reduce', 'reduceRight'], function(methodName) {
+    var func = _[methodName],
+        array = [1, 2, 3],
+        isReduce = methodName == 'reduce';
+
+    QUnit.test('`_.' + methodName + '` should reduce a collection to a single value', function(assert) {
+      assert.expect(1);
+
+      var actual = func(['a', 'b', 'c'], function(accumulator, value) {
+        return accumulator + value;
+      }, '');
+
+      assert.strictEqual(actual, isReduce ? 'abc' : 'cba');
+    });
+
+    QUnit.test('`_.' + methodName + '` should support empty collections without an initial `accumulator` value', function(assert) {
+      assert.expect(1);
+
+      var actual = [],
+          expected = lodashStable.map(empties, noop);
+
+      lodashStable.each(empties, function(value) {
+        try {
+          actual.push(func(value, noop));
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support empty collections with an initial `accumulator` value', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, lodashStable.constant('x'));
+
+      var actual = lodashStable.map(empties, function(value) {
+        try {
+          return func(value, noop, 'x');
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should handle an initial `accumulator` value of `undefined`', function(assert) {
+      assert.expect(1);
+
+      var actual = func([], noop, undefined);
+      assert.strictEqual(actual, undefined);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `undefined` for empty collections when no `accumulator` is given (test in IE > 9 and modern browsers)', function(assert) {
+      assert.expect(2);
+
+      var array = [],
+          object = { '0': 1, 'length': 0 };
+
+      if ('__proto__' in array) {
+        array.__proto__ = object;
+        assert.strictEqual(func(array, noop), undefined);
+      }
+      else {
+        skipAssert(assert);
+      }
+      assert.strictEqual(func(object, noop), undefined);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.strictEqual(_(array)[methodName](add), 6);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(array).chain()[methodName](add) instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.reject');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should return elements the `predicate` returns falsey for', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.reject(array, isEven), [1, 3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('filter methods');
+
+  lodashStable.each(['filter', 'reject'], function(methodName) {
+    var array = [1, 2, 3, 4],
+        func = _[methodName],
+        isFilter = methodName == 'filter',
+        objects = [{ 'a': 0 }, { 'a': 1 }];
+
+    QUnit.test('`_.' + methodName + '` should not modify the resulting value from within `predicate`', function(assert) {
+      assert.expect(1);
+
+      var actual = func([0], function(value, index, array) {
+        array[index] = 1;
+        return isFilter;
+      });
+
+      assert.deepEqual(actual, [0]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(objects, 'a'), [objects[isFilter ? 1 : 0]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(objects, objects[1]), [objects[isFilter ? 1 : 0]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not modify wrapped values', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _(array);
+
+        var actual = wrapped[methodName](function(n) {
+          return n < 3;
+        });
+
+        assert.deepEqual(actual.value(), isFilter ? [1, 2] : [3, 4]);
+
+        actual = wrapped[methodName](function(n) {
+          return n > 2;
+        });
+
+        assert.deepEqual(actual.value(), isFilter ? [3, 4] : [1, 2]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should work in a lazy sequence', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            predicate = function(value) { return isFilter ? isEven(value) : !isEven(value); },
+            actual = _(array).slice(1).map(square)[methodName](predicate).value();
+
+        assert.deepEqual(actual, _[methodName](lodashStable.map(array.slice(1), square), predicate));
+
+        var object = lodashStable.zipObject(lodashStable.times(LARGE_ARRAY_SIZE, function(index) {
+          return ['key' + index, index];
+        }));
+
+        actual = _(object).mapValues(square)[methodName](predicate).value();
+        assert.deepEqual(actual, _[methodName](lodashStable.mapValues(object, square), predicate));
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should provide the correct `predicate` arguments in a lazy sequence', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var args,
+            array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            expected = [1, 0, lodashStable.map(array.slice(1), square)];
+
+        _(array).slice(1)[methodName](function(value, index, array) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, [1, 0, array.slice(1)]);
+
+        args = undefined;
+        _(array).slice(1).map(square)[methodName](function(value, index, array) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        args = undefined;
+        _(array).slice(1).map(square)[methodName](function(value, index) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        args = undefined;
+        _(array).slice(1).map(square)[methodName](function(value) {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, [1]);
+
+        args = undefined;
+        _(array).slice(1).map(square)[methodName](function() {
+          args || (args = slice.call(arguments));
+        }).value();
+
+        assert.deepEqual(args, expected);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.remove');
+
+  (function() {
+    QUnit.test('should modify the array and return removed elements', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4];
+
+      var actual = _.remove(array, function(n) {
+        return n % 2 == 0;
+      });
+
+      assert.deepEqual(array, [1, 3]);
+      assert.deepEqual(actual, [2, 4]);
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments', function(assert) {
+      assert.expect(1);
+
+      var argsList = [],
+          array = [1, 2, 3],
+          clone = array.slice();
+
+      _.remove(array, function(n, index) {
+        var args = slice.call(arguments);
+        args[2] = args[2].slice();
+        argsList.push(args);
+        return isEven(index);
+      });
+
+      assert.deepEqual(argsList, [[1, 0, clone], [2, 1, clone], [3, 2, clone]]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': 0, 'b': 1 }, { 'a': 1, 'b': 2 }];
+      _.remove(objects, { 'a': 1 });
+      assert.deepEqual(objects, [{ 'a': 0, 'b': 1 }]);
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': 0, 'b': 1 }, { 'a': 1, 'b': 2 }];
+      _.remove(objects, ['a', 1]);
+      assert.deepEqual(objects, [{ 'a': 0, 'b': 1 }]);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'a': 0 }, { 'a': 1 }];
+      _.remove(objects, 'a');
+      assert.deepEqual(objects, [{ 'a': 0 }]);
+    });
+
+    QUnit.test('should preserve holes in arrays', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3, 4];
+      delete array[1];
+      delete array[3];
+
+      _.remove(array, function(n) {
+        return n === 1;
+      });
+
+      assert.notOk('0' in array);
+      assert.notOk('2' in array);
+    });
+
+    QUnit.test('should treat holes as `undefined`', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+      delete array[1];
+
+      _.remove(array, function(n) {
+        return n == null;
+      });
+
+      assert.deepEqual(array, [1, 3]);
+    });
+
+    QUnit.test('should not mutate the array until all elements to remove are determined', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3];
+
+      _.remove(array, function(n, index) {
+        return isEven(index);
+      });
+
+      assert.deepEqual(array, [2]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.repeat');
+
+  (function() {
+    var string = 'abc';
+
+    QUnit.test('should repeat a string `n` times', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.repeat('*', 3), '***');
+      assert.strictEqual(_.repeat(string, 2), 'abcabc');
+    });
+
+    QUnit.test('should treat falsey `n` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? string : '';
+      });
+
+      var actual = lodashStable.map(falsey, function(n, index) {
+        return index ? _.repeat(string, n) : _.repeat(string);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an empty string if `n` is <= `0`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.repeat(string, 0), '');
+      assert.strictEqual(_.repeat(string, -2), '');
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.repeat(string, '2'), 'abcabc');
+      assert.strictEqual(_.repeat(string, 2.6), 'abcabc');
+      assert.strictEqual(_.repeat('*', { 'valueOf': alwaysThree }), '***');
+    });
+
+    QUnit.test('should coerce `string` to a string', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.repeat(Object(string), 2), 'abcabc');
+      assert.strictEqual(_.repeat({ 'toString': lodashStable.constant('*') }, 3), '***');
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(['a', 'b', 'c'], _.repeat);
+      assert.deepEqual(actual, ['a', 'b', 'c']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.replace');
+
+  (function() {
+    QUnit.test('should replace the matched pattern', function(assert) {
+      assert.expect(2);
+
+      var string = 'abcde';
+      assert.strictEqual(_.replace(string, 'de', '123'), 'abc123');
+      assert.strictEqual(_.replace(string, /[bd]/g, '-'), 'a-c-e');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.result');
+
+  (function() {
+    var object = { 'a': 1, 'b': alwaysB };
+
+    QUnit.test('should invoke function values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.result(object, 'b'), 'b');
+    });
+
+    QUnit.test('should invoke default function values', function(assert) {
+      assert.expect(1);
+
+      var actual = _.result(object, 'c', object.b);
+      assert.strictEqual(actual, 'b');
+    });
+
+    QUnit.test('should invoke nested function values', function(assert) {
+      assert.expect(2);
+
+      var value = { 'a': lodashStable.constant({ 'b': alwaysB }) };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(_.result(value, path), 'b');
+      });
+    });
+
+    QUnit.test('should invoke deep property methods with the correct `this` binding', function(assert) {
+      assert.expect(2);
+
+      var value = { 'a': { 'b': function() { return this.c; }, 'c': 1 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(_.result(value, path), 1);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.get and lodash.result');
+
+  lodashStable.each(['get', 'result'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should get string keyed property values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1 };
+
+      lodashStable.each(['a', ['a']], function(path) {
+        assert.strictEqual(func(object, path), 1);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var object = { '-0': 'a', '0': 'b' },
+          props = [-0, Object(-0), 0, Object(0)];
+
+      var actual = lodashStable.map(props, function(key) {
+        return func(object, key);
+      });
+
+      assert.deepEqual(actual, ['a', 'a', 'b', 'b']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should get symbol keyed property values', function(assert) {
+      assert.expect(1);
+
+      if (Symbol) {
+        var object = {};
+        object[symbol] = 1;
+
+        assert.strictEqual(func(object, symbol), 1);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should get deep property values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(func(object, path), 2);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should get a key over a path', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a.b': 1, 'a': { 'b': 2 } };
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        assert.strictEqual(func(object, path), 1);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not coerce array paths to strings', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a,b,c': 3, 'a': { 'b': { 'c': 4 } } };
+      assert.strictEqual(func(object, ['a', 'b', 'c']), 4);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore empty brackets', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1 };
+      assert.strictEqual(func(object, 'a[]'), 1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should handle empty paths', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([['', ''], [[], ['']]], function(pair) {
+        assert.strictEqual(func({}, pair[0]), undefined);
+        assert.strictEqual(func({ '': 3 }, pair[1]), 3);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should handle complex paths', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { '-1.23': { '["b"]': { 'c': { "['d']": { '\ne\n': { 'f': { 'g': 8 } } } } } } } };
+
+      var paths = [
+        'a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g',
+        ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g']
+      ];
+
+      lodashStable.each(paths, function(path) {
+        assert.strictEqual(func(object, path), 8);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `undefined` when `object` is nullish', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['constructor', ['constructor']], function(path) {
+        assert.strictEqual(func(null, path), undefined);
+        assert.strictEqual(func(undefined, path), undefined);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `undefined` with deep paths when `object` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [null, undefined],
+          expected = lodashStable.map(values, noop),
+          paths = ['constructor.prototype.valueOf', ['constructor', 'prototype', 'valueOf']];
+
+      lodashStable.each(paths, function(path) {
+        var actual = lodashStable.map(values, function(value) {
+          return func(value, path);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `undefined` if parts of `path` are missing', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': [, null] };
+
+      lodashStable.each(['a[1].b.c', ['a', '1', 'b', 'c']], function(path) {
+        assert.strictEqual(func(object, path), undefined);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should be able to return `null` values', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { 'b': null } };
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        assert.strictEqual(func(object, path), null);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should follow `path` over non-plain objects', function(assert) {
+      assert.expect(2);
+
+      var paths = ['a.b', ['a', 'b']];
+
+      lodashStable.each(paths, function(path) {
+        numberProto.a = { 'b': 2 };
+        assert.strictEqual(func(0, path), 2);
+        delete numberProto.a;
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should return the default value for `undefined` values', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': {} },
+          values = empties.concat(true, new Date, 1, /x/, 'a');
+
+      var expected = lodashStable.transform(values, function(result, value) {
+        result.push(value, value, value, value);
+      });
+
+      var actual = lodashStable.transform(values, function(result, value) {
+        lodashStable.each(['a.b', ['a', 'b']], function(path) {
+          result.push(
+            func(object, path, value),
+            func(null, path, value)
+          );
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return the default value when `path` is empty', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func({}, [], 'a'), 'a');
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.rest');
+
+  (function() {
+    function fn(a, b, c) {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should apply a rest parameter to `func`', function(assert) {
+      assert.expect(1);
+
+      var rest = _.rest(fn);
+      assert.deepEqual(rest(1, 2, 3, 4), [1, 2, [3, 4]]);
+    });
+
+    QUnit.test('should work with `start`', function(assert) {
+      assert.expect(1);
+
+      var rest = _.rest(fn, 1);
+      assert.deepEqual(rest(1, 2, 3, 4), [1, [2, 3, 4]]);
+    });
+
+    QUnit.test('should treat `start` as `0` for negative or `NaN` values', function(assert) {
+      assert.expect(1);
+
+      var values = [-1, NaN, 'a'],
+          expected = lodashStable.map(values, lodashStable.constant([[1, 2, 3, 4]]));
+
+      var actual = lodashStable.map(values, function(value) {
+        var rest = _.rest(fn, value);
+        return rest(1, 2, 3, 4);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce `start` to an integer', function(assert) {
+      assert.expect(1);
+
+      var rest = _.rest(fn, 1.6);
+      assert.deepEqual(rest(1, 2, 3), [1, [2, 3]]);
+    });
+
+    QUnit.test('should use an empty array when `start` is not reached', function(assert) {
+      assert.expect(1);
+
+      var rest = _.rest(fn);
+      assert.deepEqual(rest(1), [1, undefined, []]);
+    });
+
+    QUnit.test('should work on functions with more than three parameters', function(assert) {
+      assert.expect(1);
+
+      var rest = _.rest(function(a, b, c, d) {
+        return slice.call(arguments);
+      });
+
+      assert.deepEqual(rest(1, 2, 3, 4, 5), [1, 2, 3, [4, 5]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.reverse');
+
+  (function() {
+    var largeArray = lodashStable.range(LARGE_ARRAY_SIZE).concat(null),
+        smallArray = [0, 1, 2, null];
+
+    QUnit.test('should reverse `array`', function(assert) {
+      assert.expect(2);
+
+      var array = [1, 2, 3],
+          actual = _.reverse(array);
+
+      assert.deepEqual(array, [3, 2, 1]);
+      assert.strictEqual(actual, array);
+    });
+
+    QUnit.test('should return the wrapped reversed `array`', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        lodashStable.times(2, function(index) {
+          var array = (index ? largeArray : smallArray).slice(),
+              clone = array.slice(),
+              wrapped = _(array).reverse(),
+              actual = wrapped.value();
+
+          assert.ok(wrapped instanceof _);
+          assert.strictEqual(actual, array);
+          assert.deepEqual(actual, clone.slice().reverse());
+        });
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        lodashStable.times(2, function(index) {
+          var array = (index ? largeArray : smallArray).slice(),
+              expected = array.slice(),
+              actual = _(array).slice(1).reverse().value();
+
+          assert.deepEqual(actual, expected.slice(1).reverse());
+          assert.deepEqual(array, expected);
+        });
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should be lazy when in a lazy sequence', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var spy = {
+          'toString': function() {
+            throw new Error('spy was revealed');
+          }
+        };
+
+        var array = largeArray.concat(spy),
+            expected = array.slice();
+
+        try {
+          var wrapped = _(array).slice(1).map(String).reverse(),
+              actual = wrapped.last();
+        } catch (e) {}
+
+        assert.ok(wrapped instanceof _);
+        assert.strictEqual(actual, '1');
+        assert.deepEqual(array, expected);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should work in a hybrid sequence', function(assert) {
+      assert.expect(8);
+
+      if (!isNpm) {
+        lodashStable.times(2, function(index) {
+          var clone = (index ? largeArray : smallArray).slice();
+
+          lodashStable.each(['map', 'filter'], function(methodName) {
+            var array = clone.slice(),
+                expected = clone.slice(1, -1).reverse(),
+                actual = _(array)[methodName](identity).thru(_.compact).reverse().value();
+
+            assert.deepEqual(actual, expected);
+
+            array = clone.slice();
+            actual = _(array).thru(_.compact)[methodName](identity).pull(1).push(3).reverse().value();
+
+            assert.deepEqual(actual, [3].concat(expected.slice(0, -1)));
+          });
+        });
+      }
+      else {
+        skipAssert(assert, 8);
+      }
+    });
+
+    QUnit.test('should track the `__chain__` value of a wrapper', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        lodashStable.times(2, function(index) {
+          var array = (index ? largeArray : smallArray).slice(),
+              expected = array.slice().reverse(),
+              wrapped = _(array).chain().reverse().head();
+
+          assert.ok(wrapped instanceof _);
+          assert.strictEqual(wrapped.value(), _.head(expected));
+          assert.deepEqual(array, expected);
+        });
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('round methods');
+
+  lodashStable.each(['ceil', 'floor', 'round'], function(methodName) {
+    var func = _[methodName],
+        isCeil = methodName == 'ceil',
+        isFloor = methodName == 'floor';
+
+    QUnit.test('`_.' + methodName + '` should return a rounded number without a precision', function(assert) {
+      assert.expect(1);
+
+      var actual = func(4.006);
+      assert.strictEqual(actual, isCeil ? 5 : 4);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a precision of `0`', function(assert) {
+      assert.expect(1);
+
+      var actual = func(4.006, 0);
+      assert.strictEqual(actual, isCeil ? 5 : 4);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a positive precision', function(assert) {
+      assert.expect(2);
+
+      var actual = func(4.016, 2);
+      assert.strictEqual(actual, isFloor ? 4.01 : 4.02);
+
+      actual = func(4.1, 2);
+      assert.strictEqual(actual, 4.1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a negative precision', function(assert) {
+      assert.expect(1);
+
+      var actual = func(4160, -2);
+      assert.strictEqual(actual, isFloor ? 4100 : 4200);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `precision` to an integer', function(assert) {
+      assert.expect(3);
+
+      var actual = func(4.006, NaN);
+      assert.strictEqual(actual, isCeil ? 5 : 4);
+
+      var expected = isFloor ? 4.01 : 4.02;
+
+      actual = func(4.016, 2.6);
+      assert.strictEqual(actual, expected);
+
+      actual = func(4.016, '+2');
+      assert.strictEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with exponential notation and `precision`', function(assert) {
+      assert.expect(3);
+
+      var actual = func(5e1, 2);
+      assert.deepEqual(actual, 50);
+
+      actual = func('5e', 1);
+      assert.deepEqual(actual, NaN);
+
+      actual = func('5e1e1', 1);
+      assert.deepEqual(actual, NaN);
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var values = [[0], [-0], ['0'], ['-0'], [0, 1], [-0, 1], ['0', 1], ['-0', 1]],
+          expected = [Infinity, -Infinity, Infinity, -Infinity, Infinity, -Infinity, Infinity, -Infinity];
+
+      var actual = lodashStable.map(values, function(args) {
+        return 1 / func.apply(undefined, args);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.runInContext');
+
+  (function() {
+    QUnit.test('should not require a fully populated `context` object', function(assert) {
+      assert.expect(1);
+
+      if (!isModularize) {
+        var lodash = _.runInContext({
+          'setTimeout': function(callback) {
+            callback();
+          }
+        });
+
+        var pass = false;
+        lodash.delay(function() { pass = true; }, 32);
+        assert.ok(pass);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should use a zeroed `_.uniqueId` counter', function(assert) {
+      assert.expect(3);
+
+      if (!isModularize) {
+        lodashStable.times(2, _.uniqueId);
+
+        var oldId = Number(_.uniqueId()),
+            lodash = _.runInContext();
+
+        assert.ok(_.uniqueId() > oldId);
+
+        var id = lodash.uniqueId();
+        assert.strictEqual(id, '1');
+        assert.ok(id < oldId);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.sample');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should return a random element', function(assert) {
+      assert.expect(1);
+
+      var actual = _.sample(array);
+      assert.ok(lodashStable.includes(array, actual));
+    });
+
+    QUnit.test('should return `undefined` when sampling empty collections', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, noop);
+
+      var actual = lodashStable.transform(empties, function(result, value) {
+        try {
+          result.push(_.sample(value));
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should sample an object', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          actual = _.sample(object);
+
+      assert.ok(lodashStable.includes(array, actual));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.sampleSize');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should return an array of random elements', function(assert) {
+      assert.expect(2);
+
+      var actual = _.sampleSize(array, 2);
+
+      assert.strictEqual(actual.length, 2);
+      assert.deepEqual(lodashStable.difference(actual, array), []);
+    });
+
+    QUnit.test('should contain elements of the collection', function(assert) {
+      assert.expect(1);
+
+      var actual = _.sampleSize(array, array.length).sort();
+
+      assert.deepEqual(actual, array);
+    });
+
+    QUnit.test('should treat falsey `size` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? ['a'] : [];
+      });
+
+      var actual = lodashStable.map(falsey, function(size, index) {
+        return index ? _.sampleSize(['a'], size) : _.sampleSize(['a']);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an empty array when `n` < `1` or `NaN`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([0, -1, -Infinity], function(n) {
+        assert.deepEqual(_.sampleSize(array, n), []);
+      });
+    });
+
+    QUnit.test('should return all elements when `n` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) {
+        var actual = _.sampleSize(array, n).sort();
+        assert.deepEqual(actual, array);
+      });
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(1);
+
+      var actual = _.sampleSize(array, 1.6);
+      assert.strictEqual(actual.length, 1);
+    });
+
+    QUnit.test('should return an empty array for empty collections', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, alwaysEmptyArray);
+
+      var actual = lodashStable.transform(empties, function(result, value) {
+        try {
+          result.push(_.sampleSize(value, 1));
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should sample an object', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': 1, 'b': 2, 'c': 3 },
+          actual = _.sampleSize(object, 2);
+
+      assert.strictEqual(actual.length, 2);
+      assert.deepEqual(lodashStable.difference(actual, lodashStable.values(object)), []);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map([['a']], _.sampleSize);
+      assert.deepEqual(actual, [['a']]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.setWith');
+
+  (function() {
+    QUnit.test('should work with a `customizer` callback', function(assert) {
+      assert.expect(1);
+
+      var actual = _.setWith({ '0': {} }, '[0][1][2]', 3, function(value) {
+        return lodashStable.isObject(value) ? undefined : {};
+      });
+
+      assert.deepEqual(actual, { '0': { '1': { '2': 3 } } });
+    });
+
+    QUnit.test('should work with a `customizer` that returns `undefined`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.setWith({}, 'a[0].b.c', 4, noop);
+      assert.deepEqual(actual, { 'a': [{ 'b': { 'c': 4 } }] });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('set methods');
+
+  lodashStable.each(['update', 'updateWith', 'set', 'setWith'], function(methodName) {
+    var func = _[methodName],
+        isUpdate = methodName == 'update' || methodName == 'updateWith';
+
+    var oldValue = 1,
+        value = 2,
+        updater = isUpdate ? lodashStable.constant(value) : value;
+
+    QUnit.test('`_.' + methodName + '` should set property values', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var object = { 'a': oldValue },
+            actual = func(object, path, updater);
+
+        assert.strictEqual(actual, object);
+        assert.strictEqual(object.a, value);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var props = [-0, Object(-0), 0, Object(0)],
+          expected = lodashStable.map(props, lodashStable.constant(value));
+
+      var actual = lodashStable.map(props, function(key) {
+        var object = { '-0': 'a', '0': 'b' };
+        func(object, key, updater);
+        return object[lodashStable.toString(key)];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should unset symbol keyed property values', function(assert) {
+      assert.expect(2);
+
+      if (Symbol) {
+        var object = {};
+        object[symbol] = 1;
+
+        assert.strictEqual(_.unset(object, symbol), true);
+        assert.notOk(symbol in object);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should set deep property values', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var object = { 'a': { 'b': oldValue } },
+            actual = func(object, path, updater);
+
+        assert.strictEqual(actual, object);
+        assert.strictEqual(object.a.b, value);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should set a key over a path', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a.b', ['a.b']], function(path) {
+        var object = { 'a.b': oldValue },
+            actual = func(object, path, updater);
+
+        assert.strictEqual(actual, object);
+        assert.deepEqual(object, { 'a.b': value });
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not coerce array paths to strings', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a,b,c': 1, 'a': { 'b': { 'c': 1 } } };
+
+      func(object, ['a', 'b', 'c'], updater);
+      assert.strictEqual(object.a.b.c, value);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore empty brackets', function(assert) {
+      assert.expect(1);
+
+      var object = {};
+
+      func(object, 'a[]', updater);
+      assert.deepEqual(object, { 'a': value });
+    });
+
+    QUnit.test('`_.' + methodName + '` should handle empty paths', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([['', ''], [[], ['']]], function(pair, index) {
+        var object = {};
+
+        func(object, pair[0], updater);
+        assert.deepEqual(object, index ? {} : { '': value });
+
+        func(object, pair[1], updater);
+        assert.deepEqual(object, { '': value });
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should handle complex paths', function(assert) {
+      assert.expect(2);
+
+      var object = { 'a': { '1.23': { '["b"]': { 'c': { "['d']": { '\ne\n': { 'f': { 'g': oldValue } } } } } } } };
+
+      var paths = [
+        'a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g',
+        ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g']
+      ];
+
+      lodashStable.each(paths, function(path) {
+        func(object, path, updater);
+        assert.strictEqual(object.a[-1.23]['["b"]'].c["['d']"]['\ne\n'].f.g, value);
+        object.a[-1.23]['["b"]'].c["['d']"]['\ne\n'].f.g = oldValue;
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should create parts of `path` that are missing', function(assert) {
+      assert.expect(6);
+
+      var object = {};
+
+      lodashStable.each(['a[1].b.c', ['a', '1', 'b', 'c']], function(path) {
+        var actual = func(object, path, updater);
+
+        assert.strictEqual(actual, object);
+        assert.deepEqual(actual, { 'a': [undefined, { 'b': { 'c': value } }] });
+        assert.notOk('0' in object.a);
+
+        delete object.a;
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not error when `object` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [null, undefined],
+          expected = [[null, null], [undefined, undefined]];
+
+      var actual = lodashStable.map(values, function(value) {
+        try {
+          return [func(value, 'a.b', updater), func(value, ['a', 'b'], updater)];
+        } catch (e) {
+          return e.message;
+        }
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should follow `path` over non-plain objects', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': '' },
+          paths = ['constructor.prototype.a', ['constructor', 'prototype', 'a']];
+
+      lodashStable.each(paths, function(path) {
+        func(0, path, updater);
+        assert.strictEqual(0..a, value);
+        delete numberProto.a;
+      });
+
+      lodashStable.each(['a.replace.b', ['a', 'replace', 'b']], function(path) {
+        func(object, path, updater);
+        assert.strictEqual(stringProto.replace.b, value);
+        delete stringProto.replace.b;
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not error on paths over primitives in strict mode', function(assert) {
+      'use strict';
+
+      assert.expect(2);
+
+      lodashStable.each(['a', 'a.a.a'], function(path) {
+        numberProto.a = oldValue;
+        try {
+          func(0, path, updater);
+          assert.strictEqual(0..a, oldValue);
+        } catch (e) {
+          assert.ok(false, e.message);
+        }
+      });
+
+      delete numberProto.a;
+    });
+
+    QUnit.test('`_.' + methodName + '` should not create an array for missing non-index property names that start with numbers', function(assert) {
+      assert.expect(1);
+
+      var object = {};
+
+      func(object, ['1a', '2b', '3c'], updater);
+      assert.deepEqual(object, { '1a': { '2b': { '3c': value } } });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not assign values that are the same as their destinations', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a', ['a'], { 'a': 1 }, NaN], function(value) {
+        if (defineProperty) {
+          var object = {},
+              pass = true,
+              updater = isUpdate ? lodashStable.constant(value) : value;
+
+          defineProperty(object, 'a', {
+            'enumerable': true,
+            'configurable': true,
+            'get': lodashStable.constant(value),
+            'set': function() { pass = false; }
+          });
+
+          func(object, 'a', updater);
+          assert.ok(pass);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.shuffle');
+
+  (function() {
+    var array = [1, 2, 3],
+        object = { 'a': 1, 'b': 2, 'c': 3 };
+
+    QUnit.test('should return a new array', function(assert) {
+      assert.expect(1);
+
+      assert.notStrictEqual(_.shuffle(array), array);
+    });
+
+    QUnit.test('should contain the same elements after a collection is shuffled', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.shuffle(array).sort(), array);
+      assert.deepEqual(_.shuffle(object).sort(), array);
+    });
+
+    QUnit.test('should shuffle small collections', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.times(1000, function(assert) {
+        return _.shuffle([1, 2]);
+      });
+
+      assert.deepEqual(lodashStable.sortBy(lodashStable.uniqBy(actual, String), '0'), [[1, 2], [2, 1]]);
+    });
+
+    QUnit.test('should treat number values for `collection` as empty', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.shuffle(1), []);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.size');
+
+  (function() {
+    var args = arguments,
+        array = [1, 2, 3];
+
+    QUnit.test('should return the number of own enumerable string keyed properties of an object', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.size({ 'one': 1, 'two': 2, 'three': 3 }), 3);
+    });
+
+    QUnit.test('should return the length of an array', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.size(array), 3);
+    });
+
+    QUnit.test('should accept a falsey `object` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysZero);
+
+      var actual = lodashStable.map(falsey, function(object, index) {
+        try {
+          return index ? _.size(object) : _.size();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.size(args), 3);
+    });
+
+    QUnit.test('should work with jQuery/MooTools DOM query collections', function(assert) {
+      assert.expect(1);
+
+      function Foo(elements) {
+        push.apply(this, elements);
+      }
+      Foo.prototype = { 'length': 0, 'splice': arrayProto.splice };
+
+      assert.strictEqual(_.size(new Foo(array)), 3);
+    });
+
+    QUnit.test('should work with maps', function(assert) {
+      assert.expect(2);
+
+      if (Map) {
+        lodashStable.each([new Map, realm.map], function(map) {
+          map.set('a', 1);
+          map.set('b', 2);
+          assert.strictEqual(_.size(map), 2);
+          map.clear();
+        });
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should work with sets', function(assert) {
+      assert.expect(2);
+
+      if (Set) {
+        lodashStable.each([new Set, realm.set], function(set) {
+          set.add(1);
+          set.add(2);
+          assert.strictEqual(_.size(set), 2);
+          set.clear();
+        });
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should not treat objects with negative lengths as array-like', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.size({ 'length': -1 }), 1);
+    });
+
+    QUnit.test('should not treat objects with lengths larger than `MAX_SAFE_INTEGER` as array-like', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.size({ 'length': MAX_SAFE_INTEGER + 1 }), 1);
+    });
+
+    QUnit.test('should not treat objects with non-number lengths as array-like', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.size({ 'length': '0' }), 1);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.slice');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should use a default `start` of `0` and a default `end` of `array.length`', function(assert) {
+      assert.expect(2);
+
+      var actual = _.slice(array);
+      assert.deepEqual(actual, array);
+      assert.notStrictEqual(actual, array);
+    });
+
+    QUnit.test('should work with a positive `start`', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.slice(array, 1), [2, 3]);
+      assert.deepEqual(_.slice(array, 1, 3), [2, 3]);
+    });
+
+    QUnit.test('should work with a `start` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(start) {
+        assert.deepEqual(_.slice(array, start), []);
+      });
+    });
+
+    QUnit.test('should treat falsey `start` values as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, lodashStable.constant(array));
+
+      var actual = lodashStable.map(falsey, function(start) {
+        return _.slice(array, start);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with a negative `start`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.slice(array, -1), [3]);
+    });
+
+    QUnit.test('should work with a negative `start` <= negative `array.length`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([-3, -4, -Infinity], function(start) {
+        assert.deepEqual(_.slice(array, start), array);
+      });
+    });
+
+    QUnit.test('should work with `start` >= `end`', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([2, 3], function(start) {
+        assert.deepEqual(_.slice(array, start, 2), []);
+      });
+    });
+
+    QUnit.test('should work with a positive `end`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.slice(array, 0, 1), [1]);
+    });
+
+    QUnit.test('should work with a `end` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(end) {
+        assert.deepEqual(_.slice(array, 0, end), array);
+      });
+    });
+
+    QUnit.test('should treat falsey `end` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? array : [];
+      });
+
+      var actual = lodashStable.map(falsey, function(end, index) {
+        return index ? _.slice(array, 0, end) : _.slice(array, 0);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with a negative `end`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.slice(array, 0, -1), [1, 2]);
+    });
+
+    QUnit.test('should work with a negative `end` <= negative `array.length`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([-3, -4, -Infinity], function(end) {
+        assert.deepEqual(_.slice(array, 0, end), []);
+      });
+    });
+
+    QUnit.test('should coerce `start` and `end` to integers', function(assert) {
+      assert.expect(1);
+
+      var positions = [[0.1, 1.6], ['0', 1], [0, '1'], ['1'], [NaN, 1], [1, NaN]];
+
+      var actual = lodashStable.map(positions, function(pos) {
+        return _.slice.apply(_, [array].concat(pos));
+      });
+
+      assert.deepEqual(actual, [[1], [1], [1], [2, 3], [1], []]);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(2);
+
+      var array = [[1], [2, 3]],
+          actual = lodashStable.map(array, _.slice);
+
+      assert.deepEqual(actual, array);
+      assert.notStrictEqual(actual, array);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(38);
+
+      if (!isNpm) {
+        var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1),
+            length = array.length,
+            wrapped = _(array);
+
+        lodashStable.each(['map', 'filter'], function(methodName) {
+          assert.deepEqual(wrapped[methodName]().slice(0, -1).value(), array.slice(0, -1));
+          assert.deepEqual(wrapped[methodName]().slice(1).value(), array.slice(1));
+          assert.deepEqual(wrapped[methodName]().slice(1, 3).value(), array.slice(1, 3));
+          assert.deepEqual(wrapped[methodName]().slice(-1).value(), array.slice(-1));
+
+          assert.deepEqual(wrapped[methodName]().slice(length).value(), array.slice(length));
+          assert.deepEqual(wrapped[methodName]().slice(3, 2).value(), array.slice(3, 2));
+          assert.deepEqual(wrapped[methodName]().slice(0, -length).value(), array.slice(0, -length));
+          assert.deepEqual(wrapped[methodName]().slice(0, null).value(), array.slice(0, null));
+
+          assert.deepEqual(wrapped[methodName]().slice(0, length).value(), array.slice(0, length));
+          assert.deepEqual(wrapped[methodName]().slice(-length).value(), array.slice(-length));
+          assert.deepEqual(wrapped[methodName]().slice(null).value(), array.slice(null));
+
+          assert.deepEqual(wrapped[methodName]().slice(0, 1).value(), array.slice(0, 1));
+          assert.deepEqual(wrapped[methodName]().slice(NaN, '1').value(), array.slice(NaN, '1'));
+
+          assert.deepEqual(wrapped[methodName]().slice(0.1, 1.1).value(), array.slice(0.1, 1.1));
+          assert.deepEqual(wrapped[methodName]().slice('0', 1).value(), array.slice('0', 1));
+          assert.deepEqual(wrapped[methodName]().slice(0, '1').value(), array.slice(0, '1'));
+          assert.deepEqual(wrapped[methodName]().slice('1').value(), array.slice('1'));
+          assert.deepEqual(wrapped[methodName]().slice(NaN, 1).value(), array.slice(NaN, 1));
+          assert.deepEqual(wrapped[methodName]().slice(1, NaN).value(), array.slice(1, NaN));
+        });
+      }
+      else {
+        skipAssert(assert, 38);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.some');
+
+  (function() {
+    QUnit.test('should return `true` if `predicate` returns truthy for any element', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.some([false, 1, ''], identity), true);
+      assert.strictEqual(_.some([null, 'a', 0], identity), true);
+    });
+
+    QUnit.test('should return `false` for empty collections', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, alwaysFalse);
+
+      var actual = lodashStable.map(empties, function(value) {
+        try {
+          return _.some(value, identity);
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return `true` as soon as `predicate` returns truthy', function(assert) {
+      assert.expect(2);
+
+      var count = 0;
+
+      assert.strictEqual(_.some([null, true, null], function(value) {
+        count++;
+        return value;
+      }), true);
+
+      assert.strictEqual(count, 2);
+    });
+
+    QUnit.test('should return `false` if `predicate` returns falsey for all elements', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.some([false, false, false], identity), false);
+      assert.strictEqual(_.some([null, 0, ''], identity), false);
+    });
+
+    QUnit.test('should use `_.identity` when `predicate` is nullish', function(assert) {
+      assert.expect(2);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var array = [0, 0];
+        return index ? _.some(array, value) : _.some(array);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(values, alwaysTrue);
+      actual = lodashStable.map(values, function(value, index) {
+        var array = [0, 1];
+        return index ? _.some(array, value) : _.some(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var objects = [{ 'a': 0, 'b': 0 }, { 'a': 0, 'b': 1 }];
+      assert.strictEqual(_.some(objects, 'a'), false);
+      assert.strictEqual(_.some(objects, 'b'), true);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(2);
+
+      var objects = [{ 'a': 0, 'b': 0 }, { 'a': 1, 'b': 1}];
+      assert.strictEqual(_.some(objects, { 'a': 0 }), true);
+      assert.strictEqual(_.some(objects, { 'b': 2 }), false);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map([[1]], _.some);
+      assert.deepEqual(actual, [true]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.sortBy');
+
+  (function() {
+    var objects = [
+      { 'a': 'x', 'b': 3 },
+      { 'a': 'y', 'b': 4 },
+      { 'a': 'x', 'b': 1 },
+      { 'a': 'y', 'b': 2 }
+    ];
+
+    QUnit.test('should sort in ascending order by `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(_.sortBy(objects, function(object) {
+        return object.b;
+      }), 'b');
+
+      assert.deepEqual(actual, [1, 2, 3, 4]);
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var array = [3, 2, 1],
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([1, 2, 3]));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.sortBy(array, value) : _.sortBy(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(_.sortBy(objects.concat(undefined), 'b'), 'b');
+      assert.deepEqual(actual, [1, 2, 3, 4, undefined]);
+    });
+
+    QUnit.test('should work with an object for `collection`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.sortBy({ 'a': 1, 'b': 2, 'c': 3 }, Math.sin);
+      assert.deepEqual(actual, [3, 1, 2]);
+    });
+
+    QUnit.test('should move symbol, `null`, `undefined`, and `NaN` values to the end', function(assert) {
+      assert.expect(2);
+
+      var symbol1 = Symbol ? Symbol('a') : null,
+          symbol2 = Symbol ? Symbol('b') : null,
+          array = [NaN, undefined, null, 4, symbol1, null, 1, symbol2, undefined, 3, NaN, 2],
+          expected = [1, 2, 3, 4, symbol1, symbol2, null, null, undefined, undefined, NaN, NaN];
+
+      assert.deepEqual(_.sortBy(array), expected);
+
+      array = [NaN, undefined, symbol1, null, 'd', null, 'a', symbol2, undefined, 'c', NaN, 'b'];
+      expected = ['a', 'b', 'c', 'd', symbol1, symbol2, null, null, undefined, undefined, NaN, NaN];
+
+      assert.deepEqual(_.sortBy(array), expected);
+    });
+
+    QUnit.test('should treat number values for `collection` as empty', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.sortBy(1), []);
+    });
+
+    QUnit.test('should coerce arrays returned from `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.sortBy(objects, function(object) {
+        var result = [object.a, object.b];
+        result.toString = function() { return String(this[0]); };
+        return result;
+      });
+
+      assert.deepEqual(actual, [objects[0], objects[2], objects[1], objects[3]]);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map([[2, 1, 3], [3, 2, 1]], _.sortBy);
+      assert.deepEqual(actual, [[1, 2, 3], [1, 2, 3]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('sortBy methods');
+
+  lodashStable.each(['orderBy', 'sortBy'], function(methodName) {
+    var func = _[methodName];
+
+    function Pair(a, b, c) {
+      this.a = a;
+      this.b = b;
+      this.c = c;
+    }
+
+    var objects = [
+      { 'a': 'x', 'b': 3 },
+      { 'a': 'y', 'b': 4 },
+      { 'a': 'x', 'b': 1 },
+      { 'a': 'y', 'b': 2 }
+    ];
+
+    var stableArray = [
+      new Pair(1, 1, 1), new Pair(1, 2, 1),
+      new Pair(1, 1, 1), new Pair(1, 2, 1),
+      new Pair(1, 3, 1), new Pair(1, 4, 1),
+      new Pair(1, 5, 1), new Pair(1, 6, 1),
+      new Pair(2, 1, 2), new Pair(2, 2, 2),
+      new Pair(2, 3, 2), new Pair(2, 4, 2),
+      new Pair(2, 5, 2), new Pair(2, 6, 2),
+      new Pair(undefined, 1, 1), new Pair(undefined, 2, 1),
+      new Pair(undefined, 3, 1), new Pair(undefined, 4, 1),
+      new Pair(undefined, 5, 1), new Pair(undefined, 6, 1)
+    ];
+
+    var stableObject = lodashStable.zipObject('abcdefghijklmnopqrst'.split(''), stableArray);
+
+    QUnit.test('`_.' + methodName + '` should sort multiple properties in ascending order', function(assert) {
+      assert.expect(1);
+
+      var actual = func(objects, ['a', 'b']);
+      assert.deepEqual(actual, [objects[2], objects[0], objects[3], objects[1]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support iteratees', function(assert) {
+      assert.expect(1);
+
+      var actual = func(objects, ['a', function(object) { return object.b; }]);
+      assert.deepEqual(actual, [objects[2], objects[0], objects[3], objects[1]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should perform a stable sort (test in IE > 8 and V8)', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([stableArray, stableObject], function(value, index) {
+        var actual = func(value, ['a', 'c']);
+        assert.deepEqual(actual, stableArray, index ? 'object' : 'array');
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should not error on nullish elements', function(assert) {
+      assert.expect(1);
+
+      try {
+        var actual = func(objects.concat(null, undefined), ['a', 'b']);
+      } catch (e) {}
+
+      assert.deepEqual(actual, [objects[2], objects[0], objects[3], objects[1], null, undefined]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work as an iteratee for methods like `_.reduce`', function(assert) {
+      assert.expect(3);
+
+      var objects = [
+        { 'a': 'x', '0': 3 },
+        { 'a': 'y', '0': 4 },
+        { 'a': 'x', '0': 1 },
+        { 'a': 'y', '0': 2 }
+      ];
+
+      var funcs = [func, lodashStable.partialRight(func, 'bogus')];
+
+      lodashStable.each(['a', 0, [0]], function(props, index) {
+        var expected = lodashStable.map(funcs, lodashStable.constant(
+          index
+            ? [objects[2], objects[3], objects[0], objects[1]]
+            : [objects[0], objects[2], objects[1], objects[3]]
+        ));
+
+        var actual = lodashStable.map(funcs, function(func) {
+          return lodashStable.reduce([props], func, objects);
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('sortedIndex methods');
+
+  lodashStable.each(['sortedIndex', 'sortedLastIndex'], function(methodName) {
+    var func = _[methodName],
+        isSortedIndex = methodName == 'sortedIndex';
+
+    QUnit.test('`_.' + methodName + '` should return the insert index', function(assert) {
+      assert.expect(1);
+
+      var array = [30, 50],
+          values = [30, 40, 50],
+          expected = isSortedIndex ? [0, 1, 1] : [1, 1, 2];
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(array, value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with an array of strings', function(assert) {
+      assert.expect(1);
+
+      var array = ['a', 'c'],
+          values = ['a', 'b', 'c'],
+          expected = isSortedIndex ? [0, 1, 1] : [1, 1, 2];
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(array, value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should accept a falsey `array` argument and a `value`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, lodashStable.constant([0, 0, 0]));
+
+      var actual = lodashStable.map(falsey, function(array) {
+        return [func(array, 1), func(array, undefined), func(array, NaN)];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should align with `_.sortBy`', function(assert) {
+      assert.expect(12);
+
+      var symbol1 = Symbol ? Symbol('a') : null,
+          symbol2 = Symbol ? Symbol('b') : null,
+          expected = [1, '2', {}, symbol1, symbol2, null, undefined, NaN, NaN];
+
+      lodashStable.each([
+        [NaN, symbol1, null, 1, '2', {}, symbol2, NaN, undefined],
+        ['2', null, 1, symbol1, NaN, {}, NaN, symbol2, undefined]
+      ], function(array) {
+        assert.deepEqual(_.sortBy(array), expected);
+        assert.strictEqual(func(expected, 3), 2);
+        assert.strictEqual(func(expected, symbol1), (isSortedIndex ? 3 : (Symbol ? 5 : 6)));
+        assert.strictEqual(func(expected, null), (isSortedIndex ? (Symbol ? 5 : 3) : 6));
+        assert.strictEqual(func(expected, undefined), isSortedIndex ? 6 : 7);
+        assert.strictEqual(func(expected, NaN), isSortedIndex ? 7 : 9);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('sortedIndexBy methods');
+
+  lodashStable.each(['sortedIndexBy', 'sortedLastIndexBy'], function(methodName) {
+    var func = _[methodName],
+        isSortedIndexBy = methodName == 'sortedIndexBy';
+
+    QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      func([30, 50], 40, function(assert) {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [40]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 30 }, { 'x': 50 }],
+          actual = func(objects, { 'x': 40 }, 'x');
+
+      assert.strictEqual(actual, 1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support arrays larger than `MAX_ARRAY_LENGTH / 2`', function(assert) {
+      assert.expect(12);
+
+      lodashStable.each([Math.ceil(MAX_ARRAY_LENGTH / 2), MAX_ARRAY_LENGTH], function(length) {
+        var array = [],
+            values = [MAX_ARRAY_LENGTH, NaN, undefined];
+
+        array.length = length;
+
+        lodashStable.each(values, function(value) {
+          var steps = 0;
+
+          var actual = func(array, value, function(value) {
+            steps++;
+            return value;
+          });
+
+          var expected = (isSortedIndexBy ? !lodashStable.isNaN(value) : lodashStable.isFinite(value))
+            ? 0
+            : Math.min(length, MAX_ARRAY_INDEX);
+
+          // Avoid false fails in older Firefox.
+          if (array.length == length) {
+            assert.ok(steps == 32 || steps == 33);
+            assert.strictEqual(actual, expected);
+          }
+          else {
+            skipAssert(assert, 2);
+          }
+        });
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('sortedIndexOf methods');
+
+  lodashStable.each(['sortedIndexOf', 'sortedLastIndexOf'], function(methodName) {
+    var func = _[methodName],
+        isSortedIndexOf = methodName == 'sortedIndexOf';
+
+    QUnit.test('`_.' + methodName + '` should perform a binary search', function(assert) {
+      assert.expect(1);
+
+      var sorted = [4, 4, 5, 5, 6, 6];
+      assert.deepEqual(func(sorted, 5), isSortedIndexOf ? 2 : 3);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.sortedUniq');
+
+  (function() {
+    QUnit.test('should return unique values of a sorted array', function(assert) {
+      assert.expect(3);
+
+      var expected = [1, 2, 3];
+
+      lodashStable.each([[1, 2, 3], [1, 1, 2, 2, 3], [1, 2, 3, 3, 3, 3, 3]], function(array) {
+        assert.deepEqual(_.sortedUniq(array), expected);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.split');
+
+  (function() {
+    QUnit.test('should split a string by `separator`', function(assert) {
+      assert.expect(3);
+
+      var string = 'abcde';
+      assert.deepEqual(_.split(string, 'c'), ['ab', 'de']);
+      assert.deepEqual(_.split(string, /[bd]/), ['a', 'c', 'e']);
+      assert.deepEqual(_.split(string, '', 2), ['a', 'b']);
+    });
+
+    QUnit.test('should return an array containing an empty string for empty values', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined, ''],
+          expected = lodashStable.map(values, lodashStable.constant(['']));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.split(value) : _.split();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var strings = ['abc', 'def', 'ghi'],
+          actual = lodashStable.map(strings, _.split);
+
+      assert.deepEqual(actual, [['abc'], ['def'], ['ghi']]);
+    });
+
+    QUnit.test('should allow mixed string and array prototype methods', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _('abc');
+        assert.strictEqual(wrapped.split('b').join(','), 'a,c');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.spread');
+
+  (function() {
+    function fn(a, b, c) {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should spread arguments to `func`', function(assert) {
+      assert.expect(1);
+
+      var spread = _.spread(fn);
+      assert.deepEqual(spread([4, 2]), [4, 2]);
+    });
+
+    QUnit.test('should accept a falsey `array` argument', function(assert) {
+      assert.expect(1);
+
+      var spread = _.spread(alwaysTrue),
+          expected = lodashStable.map(falsey, alwaysTrue);
+
+      var actual = lodashStable.map(falsey, function(array, index) {
+        try {
+          return index ? spread(array) : spread();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should provide the correct `func` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      var spread = _.spread(function() {
+        args = slice.call(arguments);
+      });
+
+      spread([4, 2], 'ignored');
+      assert.deepEqual(args, [4, 2]);
+    });
+
+    QUnit.test('should work with `start`', function(assert) {
+      assert.expect(1);
+
+      var spread = _.spread(fn, 1);
+      assert.deepEqual(spread(1, [2, 3, 4]), [1, 2, 3, 4]);
+    });
+
+    QUnit.test('should treat `start` as `0` for negative or `NaN` values', function(assert) {
+      assert.expect(1);
+
+      var values = [-1, NaN, 'a'],
+          expected = lodashStable.map(values, lodashStable.constant([1, 2, 3, 4]));
+
+      var actual = lodashStable.map(values, function(value) {
+        var spread = _.spread(fn, value);
+        return spread([1, 2, 3, 4]);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should coerce `start` to an integer', function(assert) {
+      assert.expect(1);
+
+      var spread = _.spread(fn, 1.6);
+      assert.deepEqual(spread(1, [2, 3]), [1, 2, 3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.startCase');
+
+  (function() {
+    QUnit.test('should uppercase only the first character of each word', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.startCase('--foo-bar--'), 'Foo Bar');
+      assert.strictEqual(_.startCase('fooBar'), 'Foo Bar');
+      assert.strictEqual(_.startCase('__FOO_BAR__'), 'FOO BAR');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.startsWith');
+
+  (function() {
+    var string = 'abc';
+
+    QUnit.test('should return `true` if a string starts with `target`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.startsWith(string, 'a'), true);
+    });
+
+    QUnit.test('should return `false` if a string does not start with `target`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.startsWith(string, 'b'), false);
+    });
+
+    QUnit.test('should work with a `position` argument', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.startsWith(string, 'b', 1), true);
+    });
+
+    QUnit.test('should work with `position` >= `string.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 5, MAX_SAFE_INTEGER, Infinity], function(position) {
+        assert.strictEqual(_.startsWith(string, 'a', position), false);
+      });
+    });
+
+    QUnit.test('should treat falsey `position` values as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysTrue);
+
+      var actual = lodashStable.map(falsey, function(position) {
+        return _.startsWith(string, 'a', position);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should treat a negative `position` as `0`', function(assert) {
+      assert.expect(6);
+
+      lodashStable.each([-1, -3, -Infinity], function(position) {
+        assert.strictEqual(_.startsWith(string, 'a', position), true);
+        assert.strictEqual(_.startsWith(string, 'b', position), false);
+      });
+    });
+
+    QUnit.test('should coerce `position` to an integer', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.startsWith(string, 'bc', 1.2), true);
+    });
+
+    QUnit.test('should return `true` when `target` is an empty string regardless of `position`', function(assert) {
+      assert.expect(1);
+
+      assert.ok(lodashStable.every([-Infinity, NaN, -3, -1, 0, 1, 2, 3, 5, MAX_SAFE_INTEGER, Infinity], function(position) {
+        return _.startsWith(string, '', position, true);
+      }));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.startsWith and lodash.endsWith');
+
+  lodashStable.each(['startsWith', 'endsWith'], function(methodName) {
+    var func = _[methodName],
+        isStartsWith = methodName == 'startsWith';
+
+    var string = 'abc',
+        chr = isStartsWith ? 'a' : 'c';
+
+    QUnit.test('`_.' + methodName + '` should coerce `string` to a string', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(func(Object(string), chr), true);
+      assert.strictEqual(func({ 'toString': lodashStable.constant(string) }, chr), true);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `target` to a string', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(func(string, Object(chr)), true);
+      assert.strictEqual(func(string, { 'toString': lodashStable.constant(chr) }), true);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `position` to a number', function(assert) {
+      assert.expect(2);
+
+      var position = isStartsWith ? 1 : 2;
+      assert.strictEqual(func(string, 'b', Object(position)), true);
+      assert.strictEqual(func(string, 'b', { 'toString': lodashStable.constant(String(position)) }), true);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.subtract');
+
+  (function() {
+    QUnit.test('should subtract two numbers', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.subtract(6, 4), 2);
+      assert.strictEqual(_.subtract(-6, 4), -10);
+      assert.strictEqual(_.subtract(-6, -4), -2);
+    });
+
+    QUnit.test('should coerce arguments to numbers', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.subtract('6', '4'), 2);
+      assert.deepEqual(_.subtract('x', 'y'), NaN);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('math operator methods');
+
+  lodashStable.each(['add', 'divide', 'multiply', 'subtract'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return `0` when no arguments are given', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func(), 0);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with only one defined argument', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(func(6), 6);
+      assert.strictEqual(func(6, undefined), 6);
+      assert.strictEqual(func(undefined, 4), 4);
+    });
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(2);
+
+      var values = [0, '0', -0, '-0'],
+          expected = [[0, Infinity], ['0', Infinity], [-0, -Infinity], ['-0', -Infinity]];
+
+      lodashStable.times(2, function(index) {
+        var actual = lodashStable.map(values, function(value) {
+          var result = index ? func(undefined, value) : func(value);
+          return [result, 1 / result];
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert objects to `NaN`', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(func(0, {}), NaN);
+      assert.deepEqual(func({}, 0), NaN);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert symbols to `NaN`', function(assert) {
+      assert.expect(2);
+
+      if (Symbol) {
+        assert.deepEqual(func(0, symbol), NaN);
+        assert.deepEqual(func(symbol, 0), NaN);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = _(1)[methodName](2);
+        assert.notOk(actual instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var actual = _(1).chain()[methodName](2);
+        assert.ok(actual instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.sumBy');
+
+  (function() {
+    var array = [6, 4, 2],
+        objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }];
+
+    QUnit.test('should work with an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var actual = _.sumBy(objects, function(object) {
+        return object.a;
+      });
+
+      assert.deepEqual(actual, 6);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.sumBy(array, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [6]);
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var arrays = [[2], [3], [1]];
+      assert.strictEqual(_.sumBy(arrays, 0), 6);
+      assert.strictEqual(_.sumBy(objects, 'a'), 6);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('sum methods');
+
+  lodashStable.each(['sum', 'sumBy'], function(methodName) {
+    var array = [6, 4, 2],
+        func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return the sum of an array of numbers', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func(array), 12);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return `0` when passing empty `array` values', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(empties, alwaysZero);
+
+      var actual = lodashStable.map(empties, function(value) {
+        return func(value);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should skip `undefined` values', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func([1, undefined]), 1);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not skip `NaN` values', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func([1, NaN]), NaN);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not coerce values to numbers', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(func(['1', '2']), '12');
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.tail');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should accept a falsey `array` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyArray);
+
+      var actual = lodashStable.map(falsey, function(array, index) {
+        try {
+          return index ? _.tail(array) : _.tail();
+        } catch (e) {}
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should exclude the first element', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.tail(array), [2, 3]);
+    });
+
+    QUnit.test('should return an empty when querying empty arrays', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.tail([]), []);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.tail);
+
+      assert.deepEqual(actual, [[2, 3], [5, 6], [8, 9]]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            values = [];
+
+        var actual = _(array).tail().filter(function(value) {
+          values.push(value);
+          return false;
+        })
+        .value();
+
+        assert.deepEqual(actual, []);
+        assert.deepEqual(values, array.slice(1));
+
+        values = [];
+
+        actual = _(array).filter(function(value) {
+          values.push(value);
+          return isEven(value);
+        })
+        .tail()
+        .value();
+
+        assert.deepEqual(actual, _.tail(_.filter(array, isEven)));
+        assert.deepEqual(values, array);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should not execute subsequent iteratees on an empty array in a lazy sequence', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            iteratee = function() { pass = false; },
+            pass = true,
+            actual = _(array).slice(0, 1).tail().map(iteratee).value();
+
+        assert.ok(pass);
+        assert.deepEqual(actual, []);
+
+        pass = true;
+        actual = _(array).filter().slice(0, 1).tail().map(iteratee).value();
+
+        assert.ok(pass);
+        assert.deepEqual(actual, []);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.take');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should take the first two elements', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.take(array, 2), [1, 2]);
+    });
+
+    QUnit.test('should treat falsey `n` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? [1] : [];
+      });
+
+      var actual = lodashStable.map(falsey, function(n) {
+        return _.take(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an empty array when `n` < `1`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([0, -1, -Infinity], function(n) {
+        assert.deepEqual(_.take(array, n), []);
+      });
+    });
+
+    QUnit.test('should return all elements when `n` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) {
+        assert.deepEqual(_.take(array, n), array);
+      });
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.take);
+
+      assert.deepEqual(actual, [[1], [4], [7]]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var array = lodashStable.range(1, LARGE_ARRAY_SIZE + 1),
+            predicate = function(value) { values.push(value); return isEven(value); },
+            values = [],
+            actual = _(array).take(2).take().value();
+
+        assert.deepEqual(actual, _.take(_.take(array, 2)));
+
+        actual = _(array).filter(predicate).take(2).take().value();
+        assert.deepEqual(values, [1, 2]);
+        assert.deepEqual(actual, _.take(_.take(_.filter(array, predicate), 2)));
+
+        actual = _(array).take(6).takeRight(4).take(2).takeRight().value();
+        assert.deepEqual(actual, _.takeRight(_.take(_.takeRight(_.take(array, 6), 4), 2)));
+
+        values = [];
+
+        actual = _(array).take(array.length - 1).filter(predicate).take(6).takeRight(4).take(2).takeRight().value();
+        assert.deepEqual(values, [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]);
+        assert.deepEqual(actual, _.takeRight(_.take(_.takeRight(_.take(_.filter(_.take(array, array.length - 1), predicate), 6), 4), 2)));
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.takeRight');
+
+  (function() {
+    var array = [1, 2, 3];
+
+    QUnit.test('should take the last two elements', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeRight(array, 2), [2, 3]);
+    });
+
+    QUnit.test('should treat falsey `n` values, except `undefined`, as `0`', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, function(value) {
+        return value === undefined ? [3] : [];
+      });
+
+      var actual = lodashStable.map(falsey, function(n) {
+        return _.takeRight(array, n);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an empty array when `n` < `1`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([0, -1, -Infinity], function(n) {
+        assert.deepEqual(_.takeRight(array, n), []);
+      });
+    });
+
+    QUnit.test('should return all elements when `n` >= `array.length`', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each([3, 4, Math.pow(2, 32), Infinity], function(n) {
+        assert.deepEqual(_.takeRight(array, n), array);
+      });
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2, 3], [4, 5, 6], [7, 8, 9]],
+          actual = lodashStable.map(array, _.takeRight);
+
+      assert.deepEqual(actual, [[3], [6], [9]]);
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(6);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            predicate = function(value) { values.push(value); return isEven(value); },
+            values = [],
+            actual = _(array).takeRight(2).takeRight().value();
+
+        assert.deepEqual(actual, _.takeRight(_.takeRight(array)));
+
+        actual = _(array).filter(predicate).takeRight(2).takeRight().value();
+        assert.deepEqual(values, array);
+        assert.deepEqual(actual, _.takeRight(_.takeRight(_.filter(array, predicate), 2)));
+
+        actual = _(array).takeRight(6).take(4).takeRight(2).take().value();
+        assert.deepEqual(actual, _.take(_.takeRight(_.take(_.takeRight(array, 6), 4), 2)));
+
+        values = [];
+
+        actual = _(array).filter(predicate).takeRight(6).take(4).takeRight(2).take().value();
+        assert.deepEqual(values, array);
+        assert.deepEqual(actual, _.take(_.takeRight(_.take(_.takeRight(_.filter(array, predicate), 6), 4), 2)));
+      }
+      else {
+        skipAssert(assert, 6);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.takeRightWhile');
+
+  (function() {
+    var array = [1, 2, 3, 4];
+
+    var objects = [
+      { 'a': 0, 'b': 0 },
+      { 'a': 1, 'b': 1 },
+      { 'a': 2, 'b': 2 }
+    ];
+
+    QUnit.test('should take elements while `predicate` returns truthy', function(assert) {
+      assert.expect(1);
+
+      var actual = _.takeRightWhile(array, function(n) {
+        return n > 2;
+      });
+
+      assert.deepEqual(actual, [3, 4]);
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.takeRightWhile(array, function() {
+        args = slice.call(arguments);
+      });
+
+      assert.deepEqual(args, [4, 3, array]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeRightWhile(objects, { 'b': 2 }), objects.slice(2));
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeRightWhile(objects, ['b', 2]), objects.slice(2));
+    });
+
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeRightWhile(objects, 'b'), objects.slice(1));
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            predicate = function(n) { return n > 2; },
+            expected = _.takeRightWhile(array, predicate),
+            wrapped = _(array).takeRightWhile(predicate);
+
+        assert.deepEqual(wrapped.value(), expected);
+        assert.deepEqual(wrapped.reverse().value(), expected.slice().reverse());
+        assert.strictEqual(wrapped.last(), _.last(expected));
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments in a lazy sequence', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var args,
+            array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            expected = [square(LARGE_ARRAY_SIZE), LARGE_ARRAY_SIZE - 1, lodashStable.map(array.slice(1), square)];
+
+        _(array).slice(1).takeRightWhile(function(value, index, array) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, [LARGE_ARRAY_SIZE, LARGE_ARRAY_SIZE - 1, array.slice(1)]);
+
+        _(array).slice(1).map(square).takeRightWhile(function(value, index, array) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        _(array).slice(1).map(square).takeRightWhile(function(value, index) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        _(array).slice(1).map(square).takeRightWhile(function(index) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, [square(LARGE_ARRAY_SIZE)]);
+
+        _(array).slice(1).map(square).takeRightWhile(function() {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, expected);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.takeWhile');
+
+  (function() {
+    var array = [1, 2, 3, 4];
+
+    var objects = [
+      { 'a': 2, 'b': 2 },
+      { 'a': 1, 'b': 1 },
+      { 'a': 0, 'b': 0 }
+    ];
+
+    QUnit.test('should take elements while `predicate` returns truthy', function(assert) {
+      assert.expect(1);
+
+      var actual = _.takeWhile(array, function(n) {
+        return n < 3;
+      });
+
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.takeWhile(array, function() {
+        args = slice.call(arguments);
+      });
+
+      assert.deepEqual(args, [1, 0, array]);
+    });
+
+    QUnit.test('should work with `_.matches` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeWhile(objects, { 'b': 2 }), objects.slice(0, 1));
+    });
+
+    QUnit.test('should work with `_.matchesProperty` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeWhile(objects, ['b', 2]), objects.slice(0, 1));
+    });
+    QUnit.test('should work with `_.property` shorthands', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.takeWhile(objects, 'b'), objects.slice(0, 2));
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            predicate = function(n) { return n < 3; },
+            expected = _.takeWhile(array, predicate),
+            wrapped = _(array).takeWhile(predicate);
+
+        assert.deepEqual(wrapped.value(), expected);
+        assert.deepEqual(wrapped.reverse().value(), expected.slice().reverse());
+        assert.strictEqual(wrapped.last(), _.last(expected));
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence with `take`', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE);
+
+        var actual = _(array)
+          .takeWhile(function(n) { return n < 4; })
+          .take(2)
+          .takeWhile(function(n) { return n == 0; })
+          .value();
+
+        assert.deepEqual(actual, [0]);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should provide the correct `predicate` arguments in a lazy sequence', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var args,
+            array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            expected = [1, 0, lodashStable.map(array.slice(1), square)];
+
+        _(array).slice(1).takeWhile(function(value, index, array) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, [1, 0, array.slice(1)]);
+
+        _(array).slice(1).map(square).takeWhile(function(value, index, array) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        _(array).slice(1).map(square).takeWhile(function(value, index) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, expected);
+
+        _(array).slice(1).map(square).takeWhile(function(value) {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, [1]);
+
+        _(array).slice(1).map(square).takeWhile(function() {
+          args = slice.call(arguments);
+        }).value();
+
+        assert.deepEqual(args, expected);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.tap');
+
+  (function() {
+    QUnit.test('should intercept and return the given value', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var intercepted,
+            array = [1, 2, 3];
+
+        var actual = _.tap(array, function(value) {
+          intercepted = value;
+        });
+
+        assert.strictEqual(actual, array);
+        assert.strictEqual(intercepted, array);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should intercept unwrapped values and return wrapped values when chaining', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var intercepted,
+            array = [1, 2, 3];
+
+        var wrapped = _(array).tap(function(value) {
+          intercepted = value;
+          value.pop();
+        });
+
+        assert.ok(wrapped instanceof _);
+
+        wrapped.value();
+        assert.strictEqual(intercepted, array);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.template');
+
+  (function() {
+    QUnit.test('should escape values in "escape" delimiters', function(assert) {
+      assert.expect(1);
+
+      var strings = ['<p><%- value %></p>', '<p><%-value%></p>', '<p><%-\nvalue\n%></p>'],
+          expected = lodashStable.map(strings, lodashStable.constant('<p>&amp;&lt;&gt;&quot;&#39;&#96;\/</p>')),
+          data = { 'value': '&<>"\'`\/' };
+
+      var actual = lodashStable.map(strings, function(string) {
+        return _.template(string)(data);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not reference `_.escape` when "escape" delimiters are not used', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= typeof __e %>');
+      assert.strictEqual(compiled({}), 'undefined');
+    });
+
+    QUnit.test('should evaluate JavaScript in "evaluate" delimiters', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template(
+        '<ul><%\
+        for (var key in collection) {\
+          %><li><%= collection[key] %></li><%\
+        } %></ul>'
+      );
+
+      var data = { 'collection': { 'a': 'A', 'b': 'B' } },
+          actual = compiled(data);
+
+      assert.strictEqual(actual, '<ul><li>A</li><li>B</li></ul>');
+    });
+
+    QUnit.test('should support "evaluate" delimiters with single line comments (test production builds)', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<% // A code comment. %><% if (value) { %>yap<% } else { %>nope<% } %>'),
+          data = { 'value': true };
+
+      assert.strictEqual(compiled(data), 'yap');
+    });
+
+    QUnit.test('should support referencing variables declared in "evaluate" delimiters from other delimiters', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<% var b = a; %><%= b.value %>'),
+          data = { 'a': { 'value': 1 } };
+
+      assert.strictEqual(compiled(data), '1');
+    });
+
+    QUnit.test('should interpolate data properties in "interpolate" delimiters', function(assert) {
+      assert.expect(1);
+
+      var strings = ['<%= a %>BC', '<%=a%>BC', '<%=\na\n%>BC'],
+          expected = lodashStable.map(strings, lodashStable.constant('ABC')),
+          data = { 'a': 'A' };
+
+      var actual = lodashStable.map(strings, function(string) {
+        return _.template(string)(data);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should support "interpolate" delimiters with escaped values', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= a ? "a=\\"A\\"" : "" %>'),
+          data = { 'a': true };
+
+      assert.strictEqual(compiled(data), 'a="A"');
+    });
+
+    QUnit.test('should support "interpolate" delimiters containing ternary operators', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= value ? value : "b" %>'),
+          data = { 'value': 'a' };
+
+      assert.strictEqual(compiled(data), 'a');
+    });
+
+    QUnit.test('should support "interpolate" delimiters containing global values', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= typeof Math.abs %>');
+
+      try {
+        var actual = compiled();
+      } catch (e) {}
+
+      assert.strictEqual(actual, 'function');
+    });
+
+    QUnit.test('should support complex "interpolate" delimiters', function(assert) {
+      assert.expect(22);
+
+      lodashStable.forOwn({
+        '<%= a + b %>': '3',
+        '<%= b - a %>': '1',
+        '<%= a = b %>': '2',
+        '<%= !a %>': 'false',
+        '<%= ~a %>': '-2',
+        '<%= a * b %>': '2',
+        '<%= a / b %>': '0.5',
+        '<%= a % b %>': '1',
+        '<%= a >> b %>': '0',
+        '<%= a << b %>': '4',
+        '<%= a & b %>': '0',
+        '<%= a ^ b %>': '3',
+        '<%= a | b %>': '3',
+        '<%= {}.toString.call(0) %>': numberTag,
+        '<%= a.toFixed(2) %>': '1.00',
+        '<%= obj["a"] %>': '1',
+        '<%= delete a %>': 'true',
+        '<%= "a" in obj %>': 'true',
+        '<%= obj instanceof Object %>': 'true',
+        '<%= new Boolean %>': 'false',
+        '<%= typeof a %>': 'number',
+        '<%= void a %>': ''
+      },
+      function(value, key) {
+        var compiled = _.template(key),
+            data = { 'a': 1, 'b': 2 };
+
+        assert.strictEqual(compiled(data), value, key);
+      });
+    });
+
+    QUnit.test('should support ES6 template delimiters', function(assert) {
+      assert.expect(2);
+
+      var data = { 'value': 2 };
+      assert.strictEqual(_.template('1${value}3')(data), '123');
+      assert.strictEqual(_.template('${"{" + value + "\\}"}')(data), '{2}');
+    });
+
+    QUnit.test('should support the "imports" option', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= a %>', { 'imports': { 'a': 1 } });
+      assert.strictEqual(compiled({}), '1');
+    });
+
+    QUnit.test('should support the "variable" options', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template(
+        '<% _.each( data.a, function( value ) { %>' +
+            '<%= value.valueOf() %>' +
+        '<% }) %>', { 'variable': 'data' }
+      );
+
+      var data = { 'a': [1, 2, 3] };
+
+      try {
+        assert.strictEqual(compiled(data), '123');
+      } catch (e) {
+        assert.ok(false, e.message);
+      }
+    });
+
+    QUnit.test('should support custom delimiters', function(assert) {
+      assert.expect(2);
+
+      lodashStable.times(2, function(index) {
+        var settingsClone = lodashStable.clone(_.templateSettings);
+
+        var settings = lodashStable.assign(index ? _.templateSettings : {}, {
+          'escape': /\{\{-([\s\S]+?)\}\}/g,
+          'evaluate': /\{\{([\s\S]+?)\}\}/g,
+          'interpolate': /\{\{=([\s\S]+?)\}\}/g
+        });
+
+        var expected = '<ul><li>0: a &amp; A</li><li>1: b &amp; B</li></ul>',
+            compiled = _.template('<ul>{{ _.each(collection, function(value, index) {}}<li>{{= index }}: {{- value }}</li>{{}); }}</ul>', index ? null : settings),
+            data = { 'collection': ['a & A', 'b & B'] };
+
+        assert.strictEqual(compiled(data), expected);
+        lodashStable.assign(_.templateSettings, settingsClone);
+      });
+    });
+
+    QUnit.test('should support custom delimiters containing special characters', function(assert) {
+      assert.expect(2);
+
+      lodashStable.times(2, function(index) {
+        var settingsClone = lodashStable.clone(_.templateSettings);
+
+        var settings = lodashStable.assign(index ? _.templateSettings : {}, {
+          'escape': /<\?-([\s\S]+?)\?>/g,
+          'evaluate': /<\?([\s\S]+?)\?>/g,
+          'interpolate': /<\?=([\s\S]+?)\?>/g
+        });
+
+        var expected = '<ul><li>0: a &amp; A</li><li>1: b &amp; B</li></ul>',
+            compiled = _.template('<ul><? _.each(collection, function(value, index) { ?><li><?= index ?>: <?- value ?></li><? }); ?></ul>', index ? null : settings),
+            data = { 'collection': ['a & A', 'b & B'] };
+
+        assert.strictEqual(compiled(data), expected);
+        lodashStable.assign(_.templateSettings, settingsClone);
+      });
+    });
+
+    QUnit.test('should use a `with` statement by default', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= index %><%= collection[index] %><% _.each(collection, function(value, index) { %><%= index %><% }); %>'),
+          actual = compiled({ 'index': 1, 'collection': ['a', 'b', 'c'] });
+
+      assert.strictEqual(actual, '1b012');
+    });
+
+    QUnit.test('should use `_.templateSettings.imports._.templateSettings`', function(assert) {
+      assert.expect(1);
+
+      var lodash = _.templateSettings.imports._,
+          settingsClone = lodashStable.clone(lodash.templateSettings);
+
+      lodash.templateSettings = lodashStable.assign(lodash.templateSettings, {
+        'interpolate': /\{\{=([\s\S]+?)\}\}/g
+      });
+
+      var compiled = _.template('{{= a }}');
+      assert.strictEqual(compiled({ 'a': 1 }), '1');
+
+      if (settingsClone) {
+        lodashStable.assign(lodash.templateSettings, settingsClone);
+      } else {
+        delete lodash.templateSettings;
+      }
+    });
+
+    QUnit.test('should fallback to `_.templateSettings`', function(assert) {
+      assert.expect(1);
+
+      var lodash = _.templateSettings.imports._,
+          delimiter = _.templateSettings.interpolate;
+
+      _.templateSettings.imports._ = { 'escape': lodashStable.escape };
+      _.templateSettings.interpolate = /\{\{=([\s\S]+?)\}\}/g;
+
+      var compiled = _.template('{{= a }}');
+      assert.strictEqual(compiled({ 'a': 1 }), '1');
+
+      _.templateSettings.imports._ = lodash;
+      _.templateSettings.interpolate = delimiter;
+    });
+
+    QUnit.test('should ignore `null` delimiters', function(assert) {
+      assert.expect(3);
+
+      var delimiter = {
+        'escape': /\{\{-([\s\S]+?)\}\}/g,
+        'evaluate': /\{\{([\s\S]+?)\}\}/g,
+        'interpolate': /\{\{=([\s\S]+?)\}\}/g
+      };
+
+      lodashStable.forOwn({
+        'escape': '{{- a }}',
+        'evaluate': '{{ print(a) }}',
+        'interpolate': '{{= a }}'
+      },
+      function(value, key) {
+        var settings = { 'escape': null, 'evaluate': null, 'interpolate': null };
+        settings[key] = delimiter[key];
+
+        var expected = '1 <%- a %> <% print(a) %> <%= a %>',
+            compiled = _.template(value + ' <%- a %> <% print(a) %> <%= a %>', settings),
+            data = { 'a': 1 };
+
+        assert.strictEqual(compiled(data), expected);
+      });
+    });
+
+    QUnit.test('should work without delimiters', function(assert) {
+      assert.expect(1);
+
+      var expected = 'abc';
+      assert.strictEqual(_.template(expected)({}), expected);
+    });
+
+    QUnit.test('should work with `this` references', function(assert) {
+      assert.expect(2);
+
+      var compiled = _.template('a<%= this.String("b") %>c');
+      assert.strictEqual(compiled(), 'abc');
+
+      var object = { 'b': 'B' };
+      object.compiled = _.template('A<%= this.b %>C', { 'variable': 'obj' });
+      assert.strictEqual(object.compiled(), 'ABC');
+    });
+
+    QUnit.test('should work with backslashes', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= a %> \\b'),
+          data = { 'a': 'A' };
+
+      assert.strictEqual(compiled(data), 'A \\b');
+    });
+
+    QUnit.test('should work with escaped characters in string literals', function(assert) {
+      assert.expect(2);
+
+      var compiled = _.template('<% print("\'\\n\\r\\t\\u2028\\u2029\\\\") %>');
+      assert.strictEqual(compiled(), "'\n\r\t\u2028\u2029\\");
+
+      var data = { 'a': 'A' };
+      compiled = _.template('\'\n\r\t<%= a %>\u2028\u2029\\"');
+      assert.strictEqual(compiled(data), '\'\n\r\tA\u2028\u2029\\"');
+    });
+
+    QUnit.test('should handle \\u2028 & \\u2029 characters', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('\u2028<%= "\\u2028\\u2029" %>\u2029');
+      assert.strictEqual(compiled(), '\u2028\u2028\u2029\u2029');
+    });
+
+    QUnit.test('should work with statements containing quotes', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template("<%\
+        if (a == 'A' || a == \"a\") {\
+          %>'a',\"A\"<%\
+        } %>"
+      );
+
+      var data = { 'a': 'A' };
+      assert.strictEqual(compiled(data), "'a',\"A\"");
+    });
+
+    QUnit.test('should work with templates containing newlines and comments', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%\n\
+        // A code comment.\n\
+        if (value) { value += 3; }\n\
+        %><p><%= value %></p>'
+      );
+
+      assert.strictEqual(compiled({ 'value': 3 }), '<p>6</p>');
+    });
+
+    QUnit.test('should not error with IE conditional comments enabled (test with development build)', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template(''),
+          pass = true;
+
+      /*@cc_on @*/
+      try {
+        compiled();
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass);
+    });
+
+    QUnit.test('should tokenize delimiters', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<span class="icon-<%= type %>2"></span>'),
+          data = { 'type': 1 };
+
+      assert.strictEqual(compiled(data), '<span class="icon-12"></span>');
+    });
+
+    QUnit.test('should evaluate delimiters once', function(assert) {
+      assert.expect(1);
+
+      var actual = [],
+          compiled = _.template('<%= func("a") %><%- func("b") %><% func("c") %>'),
+          data = { 'func': function(value) { actual.push(value); } };
+
+      compiled(data);
+      assert.deepEqual(actual, ['a', 'b', 'c']);
+    });
+
+    QUnit.test('should match delimiters before escaping text', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<<\n a \n>>', { 'evaluate': /<<(.*?)>>/g });
+      assert.strictEqual(compiled(), '<<\n a \n>>');
+    });
+
+    QUnit.test('should resolve nullish values to an empty string', function(assert) {
+      assert.expect(3);
+
+      var compiled = _.template('<%= a %><%- a %>'),
+          data = { 'a': null };
+
+      assert.strictEqual(compiled(data), '');
+
+      data = { 'a': undefined };
+      assert.strictEqual(compiled(data), '');
+
+      data = { 'a': {} };
+      compiled = _.template('<%= a.b %><%- a.b %>');
+      assert.strictEqual(compiled(data), '');
+    });
+
+    QUnit.test('should return an empty string for empty values', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined, ''],
+          expected = lodashStable.map(values, alwaysEmptyString),
+          data = { 'a': 1 };
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var compiled = index ? _.template(value) : _.template();
+        return compiled(data);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should parse delimiters without newlines', function(assert) {
+      assert.expect(1);
+
+      var expected = '<<\nprint("<p>" + (value ? "yes" : "no") + "</p>")\n>>',
+          compiled = _.template(expected, { 'evaluate': /<<(.+?)>>/g }),
+          data = { 'value': true };
+
+      assert.strictEqual(compiled(data), expected);
+    });
+
+    QUnit.test('should support recursive calls', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('<%= a %><% a = _.template(c)(obj) %><%= a %>'),
+          data = { 'a': 'A', 'b': 'B', 'c': '<%= b %>' };
+
+      assert.strictEqual(compiled(data), 'AB');
+    });
+
+    QUnit.test('should coerce `text` argument to a string', function(assert) {
+      assert.expect(1);
+
+      var object = { 'toString': lodashStable.constant('<%= a %>') },
+          data = { 'a': 1 };
+
+      assert.strictEqual(_.template(object)(data), '1');
+    });
+
+    QUnit.test('should not modify the `options` object', function(assert) {
+      assert.expect(1);
+
+      var options = {};
+      _.template('', options);
+      assert.deepEqual(options, {});
+    });
+
+    QUnit.test('should not modify `_.templateSettings` when `options` are given', function(assert) {
+      assert.expect(2);
+
+      var data = { 'a': 1 };
+
+      assert.notOk('a' in _.templateSettings);
+      _.template('', {}, data);
+      assert.notOk('a' in _.templateSettings);
+
+      delete _.templateSettings.a;
+    });
+
+    QUnit.test('should not error for non-object `data` and `options` values', function(assert) {
+      assert.expect(2);
+
+      var pass = true;
+
+      try {
+        _.template('')(1);
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass, '`data` value');
+
+      pass = true;
+
+      try {
+        _.template('', 1)(1);
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass, '`options` value');
+    });
+
+    QUnit.test('should expose the source on compiled templates', function(assert) {
+      assert.expect(1);
+
+      var compiled = _.template('x'),
+          values = [String(compiled), compiled.source],
+          expected = lodashStable.map(values, alwaysTrue);
+
+      var actual = lodashStable.map(values, function(value) {
+        return lodashStable.includes(value, '__p');
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should expose the source on SyntaxErrors', function(assert) {
+      assert.expect(1);
+
+      try {
+        _.template('<% if x %>');
+      } catch (e) {
+        var source = e.source;
+      }
+      assert.ok(lodashStable.includes(source, '__p'));
+    });
+
+    QUnit.test('should not include sourceURLs in the source', function(assert) {
+      assert.expect(1);
+
+      var options = { 'sourceURL': '/a/b/c' },
+          compiled = _.template('x', options),
+          values = [compiled.source, undefined];
+
+      try {
+        _.template('<% if x %>', options);
+      } catch (e) {
+        values[1] = e.source;
+      }
+      var expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(values, function(value) {
+        return lodashStable.includes(value, 'sourceURL');
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = ['<%= a %>', '<%- b %>', '<% print(c) %>'],
+          compiles = lodashStable.map(array, _.template),
+          data = { 'a': 'one', 'b': '`two`', 'c': 'three' };
+
+      var actual = lodashStable.map(compiles, function(compiled) {
+        return compiled(data);
+      });
+
+      assert.deepEqual(actual, ['one', '&#96;two&#96;', 'three']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.truncate');
+
+  (function() {
+    var string = 'hi-diddly-ho there, neighborino';
+
+    QUnit.test('should use a default `length` of `30`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.truncate(string), 'hi-diddly-ho there, neighbo...');
+    });
+
+    QUnit.test('should not truncate if `string` is <= `length`', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.truncate(string, { 'length': string.length }), string);
+      assert.strictEqual(_.truncate(string, { 'length': string.length + 2 }), string);
+    });
+
+    QUnit.test('should truncate string the given length', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.truncate(string, { 'length': 24 }), 'hi-diddly-ho there, n...');
+    });
+
+    QUnit.test('should support a `omission` option', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.truncate(string, { 'omission': ' [...]' }), 'hi-diddly-ho there, neig [...]');
+    });
+
+    QUnit.test('should coerce nullish `omission` values to strings', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.truncate(string, { 'omission': null }), 'hi-diddly-ho there, neighbnull');
+      assert.strictEqual(_.truncate(string, { 'omission': undefined }), 'hi-diddly-ho there, nundefined');
+    });
+
+    QUnit.test('should support a `length` option', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.truncate(string, { 'length': 4 }), 'h...');
+    });
+
+    QUnit.test('should support a `separator` option', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.truncate(string, { 'length': 24, 'separator': ' ' }), 'hi-diddly-ho there,...');
+      assert.strictEqual(_.truncate(string, { 'length': 24, 'separator': /,? +/ }), 'hi-diddly-ho there...');
+      assert.strictEqual(_.truncate(string, { 'length': 24, 'separator': /,? +/g }), 'hi-diddly-ho there...');
+    });
+
+    QUnit.test('should treat negative `length` as `0`', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each([0, -2], function(length) {
+        assert.strictEqual(_.truncate(string, { 'length': length }), '...');
+      });
+    });
+
+    QUnit.test('should coerce `length` to an integer', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['', NaN, 4.6, '4'], function(length, index) {
+        var actual = index > 1 ? 'h...' : '...';
+        assert.strictEqual(_.truncate(string, { 'length': { 'valueOf': lodashStable.constant(length) } }), actual);
+      });
+    });
+
+    QUnit.test('should coerce `string` to a string', function(assert) {
+      assert.expect(2);
+
+      assert.strictEqual(_.truncate(Object(string), { 'length': 4 }), 'h...');
+      assert.strictEqual(_.truncate({ 'toString': lodashStable.constant(string) }, { 'length': 5 }), 'hi...');
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map([string, string, string], _.truncate),
+          truncated = 'hi-diddly-ho there, neighbo...';
+
+      assert.deepEqual(actual, [truncated, truncated, truncated]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.throttle');
+
+  (function() {
+    QUnit.test('should throttle a function', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          throttled = _.throttle(function() { callCount++; }, 32);
+
+      throttled();
+      throttled();
+      throttled();
+
+      var lastCount = callCount;
+      assert.ok(callCount > 0);
+
+      setTimeout(function() {
+        assert.ok(callCount > lastCount);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('subsequent calls should return the result of the first call', function(assert) {
+      assert.expect(5);
+
+      var done = assert.async();
+
+      var throttled = _.throttle(identity, 32),
+          result = [throttled('a'), throttled('b')];
+
+      assert.deepEqual(result, ['a', 'a']);
+
+      setTimeout(function() {
+        var result = [throttled('x'), throttled('y')];
+        assert.notEqual(result[0], 'a');
+        assert.notStrictEqual(result[0], undefined);
+
+        assert.notEqual(result[1], 'y');
+        assert.notStrictEqual(result[1], undefined);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('should clear timeout when `func` is called', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      if (!isModularize) {
+        var callCount = 0,
+            dateCount = 0;
+
+        var getTime = function() {
+          return ++dateCount == 5
+            ? Infinity
+            : +new Date;
+        };
+
+        var lodash = _.runInContext(lodashStable.assign({}, root, {
+          'Date': lodashStable.assign(function() {
+            return { 'getTime': getTime };
+          }, {
+            'now': Date.now
+          })
+        }));
+
+        var throttled = lodash.throttle(function() { callCount++; }, 32);
+
+        throttled();
+        throttled();
+
+        setTimeout(function() {
+          assert.strictEqual(callCount, 2);
+          done();
+        }, 64);
+      }
+      else {
+        skipAssert(assert);
+        done();
+      }
+    });
+
+    QUnit.test('should not trigger a trailing call when invoked once', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          throttled = _.throttle(function() { callCount++; }, 32);
+
+      throttled();
+      assert.strictEqual(callCount, 1);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+        done();
+      }, 64);
+    });
+
+    lodashStable.times(2, function(index) {
+      QUnit.test('should trigger a call when invoked repeatedly' + (index ? ' and `leading` is `false`' : ''), function(assert) {
+        assert.expect(1);
+
+        var done = assert.async();
+
+        var callCount = 0,
+            limit = (argv || isPhantom) ? 1000 : 320,
+            options = index ? { 'leading': false } : {},
+            throttled = _.throttle(function() { callCount++; }, 32, options);
+
+        var start = +new Date;
+        while ((new Date - start) < limit) {
+          throttled();
+        }
+        var actual = callCount > 1;
+        setTimeout(function() {
+          assert.ok(actual);
+          done();
+        }, 1);
+      });
+    });
+
+    QUnit.test('should trigger a second throttled call as soon as possible', function(assert) {
+      assert.expect(3);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var throttled = _.throttle(function() {
+        callCount++;
+      }, 128, { 'leading': false });
+
+      throttled();
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+        throttled();
+      }, 192);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+      }, 254);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        done();
+      }, 384);
+    });
+
+    QUnit.test('should apply default options', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          throttled = _.throttle(function() { callCount++; }, 32, {});
+
+      throttled();
+      throttled();
+      assert.strictEqual(callCount, 1);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 2);
+        done();
+      }, 128);
+    });
+
+    QUnit.test('should support a `leading` option', function(assert) {
+      assert.expect(2);
+
+      var withLeading = _.throttle(identity, 32, { 'leading': true });
+      assert.strictEqual(withLeading('a'), 'a');
+
+      var withoutLeading = _.throttle(identity, 32, { 'leading': false });
+      assert.strictEqual(withoutLeading('a'), undefined);
+    });
+
+    QUnit.test('should support a `trailing` option', function(assert) {
+      assert.expect(6);
+
+      var done = assert.async();
+
+      var withCount = 0,
+          withoutCount = 0;
+
+      var withTrailing = _.throttle(function(value) {
+        withCount++;
+        return value;
+      }, 64, { 'trailing': true });
+
+      var withoutTrailing = _.throttle(function(value) {
+        withoutCount++;
+        return value;
+      }, 64, { 'trailing': false });
+
+      assert.strictEqual(withTrailing('a'), 'a');
+      assert.strictEqual(withTrailing('b'), 'a');
+
+      assert.strictEqual(withoutTrailing('a'), 'a');
+      assert.strictEqual(withoutTrailing('b'), 'a');
+
+      setTimeout(function() {
+        assert.strictEqual(withCount, 2);
+        assert.strictEqual(withoutCount, 1);
+        done();
+      }, 256);
+    });
+
+    QUnit.test('should not update `lastCalled`, at the end of the timeout, when `trailing` is `false`', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var throttled = _.throttle(function() {
+        callCount++;
+      }, 64, { 'trailing': false });
+
+      throttled();
+      throttled();
+
+      setTimeout(function() {
+        throttled();
+        throttled();
+      }, 96);
+
+      setTimeout(function() {
+        assert.ok(callCount > 1);
+        done();
+      }, 192);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.debounce and lodash.throttle');
+
+  lodashStable.each(['debounce', 'throttle'], function(methodName) {
+    var func = _[methodName],
+        isDebounce = methodName == 'debounce';
+
+    QUnit.test('`_.' + methodName + '` should not error for non-object `options` values', function(assert) {
+      assert.expect(1);
+
+      var pass = true;
+
+      try {
+        func(noop, 32, 1);
+      } catch (e) {
+        pass = false;
+      }
+      assert.ok(pass);
+    });
+
+    QUnit.test('`_.' + methodName + '` should use a default `wait` of `0`', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          funced = func(function() { callCount++; });
+
+      funced();
+
+      setTimeout(function() {
+        funced();
+        assert.strictEqual(callCount, isDebounce ? 1 : 2);
+        done();
+      }, 32);
+    });
+
+    QUnit.test('`_.' + methodName + '` should invoke `func` with the correct `this` binding', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var actual = [],
+          object = { 'funced': func(function() { actual.push(this); }, 32) },
+          expected = lodashStable.times(isDebounce ? 1 : 2, lodashStable.constant(object));
+
+      object.funced();
+      if (!isDebounce) {
+        object.funced();
+      }
+      setTimeout(function() {
+        assert.deepEqual(actual, expected);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('`_.' + methodName + '` supports recursive calls', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var actual = [],
+          args = lodashStable.map(['a', 'b', 'c'], function(chr) { return [{}, chr]; }),
+          expected = args.slice(),
+          queue = args.slice();
+
+      var funced = func(function() {
+        var current = [this];
+        push.apply(current, arguments);
+        actual.push(current);
+
+        var next = queue.shift();
+        if (next) {
+          funced.call(next[0], next[1]);
+        }
+      }, 32);
+
+      var next = queue.shift();
+      funced.call(next[0], next[1]);
+      assert.deepEqual(actual, expected.slice(0, isDebounce ? 0 : 1));
+
+      setTimeout(function() {
+        assert.deepEqual(actual, expected.slice(0, actual.length));
+        done();
+      }, 256);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work if the system time is set backwards', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      if (!isModularize) {
+        var callCount = 0,
+            dateCount = 0;
+
+        var getTime = function() {
+          return ++dateCount === 4
+            ? +new Date(2012, 3, 23, 23, 27, 18)
+            : +new Date;
+        };
+
+        var lodash = _.runInContext(lodashStable.assign({}, root, {
+          'Date': lodashStable.assign(function() {
+            return { 'getTime': getTime, 'valueOf': getTime };
+          }, {
+            'now': Date.now
+          })
+        }));
+
+        var funced = lodash[methodName](function() {
+          callCount++;
+        }, 32);
+
+        funced();
+
+        setTimeout(function() {
+          funced();
+          assert.strictEqual(callCount, isDebounce ? 1 : 2);
+          done();
+        }, 64);
+      }
+      else {
+        skipAssert(assert);
+        done();
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should support cancelling delayed calls', function(assert) {
+      assert.expect(1);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var funced = func(function() {
+        callCount++;
+      }, 32, { 'leading': false });
+
+      funced();
+      funced.cancel();
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 0);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('`_.' + methodName + '` should reset `lastCalled` after cancelling', function(assert) {
+      assert.expect(3);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var funced = func(function() {
+        return ++callCount;
+      }, 32, { 'leading': true });
+
+      assert.strictEqual(funced(), 1);
+      funced.cancel();
+
+      assert.strictEqual(funced(), 2);
+      funced();
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 3);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support flushing delayed calls', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0;
+
+      var funced = func(function() {
+        return ++callCount;
+      }, 32, { 'leading': false });
+
+      funced();
+      assert.strictEqual(funced.flush(), 1);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 1);
+        done();
+      }, 64);
+    });
+
+    QUnit.test('`_.' + methodName + '` should noop `cancel` and `flush` when nothing is queued', function(assert) {
+      assert.expect(2);
+
+      var done = assert.async();
+
+      var callCount = 0,
+          funced = func(function() { callCount++; }, 32);
+
+      funced.cancel();
+      assert.strictEqual(funced.flush(), undefined);
+
+      setTimeout(function() {
+        assert.strictEqual(callCount, 0);
+        done();
+      }, 64);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.times');
+
+  (function() {
+    QUnit.test('should coerce non-finite `n` values to `0`', function(assert) {
+      assert.expect(3);
+
+      lodashStable.each([-Infinity, NaN, Infinity], function(n) {
+        assert.deepEqual(_.times(n), []);
+      });
+    });
+
+    QUnit.test('should coerce `n` to an integer', function(assert) {
+      assert.expect(1);
+
+      var actual = _.times(2.6, _.indentify);
+      assert.deepEqual(actual, [0, 1]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.times(1, function(assert) {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [0]);
+    });
+
+    QUnit.test('should use `_.identity` when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant([0, 1, 2]));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.times(3, value) : _.times(3);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an array of the results of each `iteratee` execution', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.times(3, doubled), [0, 2, 4]);
+    });
+
+    QUnit.test('should return an empty array for falsey and negative `n` arguments', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat(-1, -Infinity),
+          expected = lodashStable.map(values, alwaysEmptyArray);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.times(value) : _.times();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.deepEqual(_(3).times(), [0, 1, 2]);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        assert.ok(_(3).chain().times() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toArray');
+
+  (function() {
+    QUnit.test('should convert objects to arrays', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(_.toArray({ 'a': 1, 'b': 2 }), [1, 2]);
+    });
+
+    QUnit.test('should convert strings to arrays', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(_.toArray(''), []);
+      assert.deepEqual(_.toArray('ab'), ['a', 'b']);
+      assert.deepEqual(_.toArray(Object('ab')), ['a', 'b']);
+    });
+
+    QUnit.test('should convert iterables to arrays', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm && Symbol && Symbol.iterator) {
+        var object = { '0': 'a', 'length': 1 };
+        object[Symbol.iterator] = arrayProto[Symbol.iterator];
+
+        assert.deepEqual(_.toArray(object), ['a']);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            actual = _(array).slice(1).map(String).toArray().value();
+
+        assert.deepEqual(actual, lodashStable.map(array.slice(1), String));
+
+        var object = lodashStable.zipObject(lodashStable.times(LARGE_ARRAY_SIZE, function(index) {
+          return ['key' + index, index];
+        }));
+
+        actual = _(object).toArray().slice(1).map(String).value();
+        assert.deepEqual(actual, _.map(_.toArray(object).slice(1), String));
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toLower');
+
+  (function() {
+    QUnit.test('should convert whole string to lower case', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(_.toLower('--Foo-Bar--'), '--foo-bar--');
+      assert.deepEqual(_.toLower('fooBar'), 'foobar');
+      assert.deepEqual(_.toLower('__FOO_BAR__'), '__foo_bar__');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toUpper');
+
+  (function() {
+    QUnit.test('should convert whole string to upper case', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(_.toUpper('--Foo-Bar'), '--FOO-BAR');
+      assert.deepEqual(_.toUpper('fooBar'), 'FOOBAR');
+      assert.deepEqual(_.toUpper('__FOO_BAR__'), '__FOO_BAR__');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.slice and lodash.toArray');
+
+  lodashStable.each(['slice', 'toArray'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        array = [1, 2, 3],
+        func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return a dense array', function(assert) {
+      assert.expect(3);
+
+      var sparse = Array(3);
+      sparse[1] = 2;
+
+      var actual = func(sparse);
+
+      assert.ok('0' in actual);
+      assert.ok('2' in actual);
+      assert.deepEqual(actual, sparse);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat array-like objects like arrays', function(assert) {
+      assert.expect(2);
+
+      var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 3 };
+      assert.deepEqual(func(object), ['a', 'b', 'c']);
+      assert.deepEqual(func(args), array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a shallow clone of arrays', function(assert) {
+      assert.expect(2);
+
+      var actual = func(array);
+      assert.deepEqual(actual, array);
+      assert.notStrictEqual(actual, array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with a node list for `collection`', function(assert) {
+      assert.expect(1);
+
+      if (document) {
+        try {
+          var actual = func(document.getElementsByTagName('body'));
+        } catch (e) {}
+
+        assert.deepEqual(actual, [body]);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('toInteger methods');
+
+  lodashStable.each(['toInteger', 'toSafeInteger'], function(methodName) {
+    var func = _[methodName],
+        isSafe = methodName == 'toSafeInteger';
+
+    QUnit.test('`_.' + methodName + '` should convert values to integers', function(assert) {
+      assert.expect(6);
+
+      assert.strictEqual(func(-5.6), -5);
+      assert.strictEqual(func('5.6'), 5);
+      assert.strictEqual(func(), 0);
+      assert.strictEqual(func(NaN), 0);
+
+      var expected = isSafe ? MAX_SAFE_INTEGER : MAX_INTEGER;
+      assert.strictEqual(func(Infinity), expected);
+      assert.strictEqual(func(-Infinity), -expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support `value` of `-0`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(1 / func(-0), -Infinity);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toLength');
+
+  (function() {
+    QUnit.test('should return a valid length', function(assert) {
+      assert.expect(4);
+
+      assert.strictEqual(_.toLength(-1), 0);
+      assert.strictEqual(_.toLength('1'), 1);
+      assert.strictEqual(_.toLength(1.1), 1);
+      assert.strictEqual(_.toLength(MAX_INTEGER), MAX_ARRAY_LENGTH);
+    });
+
+    QUnit.test('should return `value` if a valid length', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.toLength(0), 0);
+      assert.strictEqual(_.toLength(3), 3);
+      assert.strictEqual(_.toLength(MAX_ARRAY_LENGTH), MAX_ARRAY_LENGTH);
+    });
+
+    QUnit.test('should convert `-0` to `0`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(1 / _.toLength(-0), Infinity);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('number coercion methods');
+
+  lodashStable.each(['toInteger', 'toNumber', 'toSafeInteger'], function(methodName) {
+    var func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should preserve the sign of `0`', function(assert) {
+      assert.expect(2);
+
+      var values = [0, '0', -0, '-0'],
+          expected = [[0, Infinity], [0, Infinity], [-0, -Infinity], [-0, -Infinity]];
+
+      lodashStable.times(2, function(index) {
+        var others = lodashStable.map(values, index ? Object : identity);
+
+        var actual = lodashStable.map(others, function(value) {
+          var result = func(value);
+          return [result, 1 / result];
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+  });
+
+  lodashStable.each(['toInteger', 'toLength', 'toNumber', 'toSafeInteger'], function(methodName) {
+    var func = _[methodName],
+        isToLength = methodName == 'toLength',
+        isToNumber = methodName == 'toNumber',
+        isToSafeInteger = methodName == 'toSafeInteger';
+
+    function negative(string) {
+      return '-' + string;
+    }
+
+    function pad(string) {
+      return whitespace + string + whitespace;
+    }
+
+    function positive(string) {
+      return '+' + string;
+    }
+
+    QUnit.test('`_.' + methodName + '` should pass thru primitive number values', function(assert) {
+      assert.expect(1);
+
+      var values = [0, 1, NaN];
+
+      var expected = lodashStable.map(values, function(value) {
+        return (!isToNumber && value !== value) ? 0 : value;
+      });
+
+      var actual = lodashStable.map(values, func);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert number primitives and objects to numbers', function(assert) {
+      assert.expect(1);
+
+      var values = [2, 1.2, MAX_SAFE_INTEGER, MAX_INTEGER, Infinity, NaN];
+
+      var expected = lodashStable.map(values, function(value) {
+        if (!isToNumber) {
+          if (value == 1.2) {
+            value = 1;
+          }
+          else if (value == Infinity) {
+            value = MAX_INTEGER;
+          }
+          else if (value !== value) {
+            value = 0;
+          }
+          if (isToLength || isToSafeInteger) {
+            value = Math.min(value, isToLength ? MAX_ARRAY_LENGTH : MAX_SAFE_INTEGER);
+          }
+        }
+        var neg = isToLength ? 0 : -value;
+        return [value, value, neg, neg];
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        return [func(value), func(Object(value)), func(-value), func(Object(-value))];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert string primitives and objects to numbers', function(assert) {
+      assert.expect(1);
+
+      var transforms = [identity, pad, positive, negative];
+
+      var values = [
+        '10', '1.234567890', (MAX_SAFE_INTEGER + ''),
+        '1e+308', '1e308', '1E+308', '1E308',
+        '5e-324', '5E-324',
+        'Infinity', 'NaN'
+      ];
+
+      var expected = lodashStable.map(values, function(value) {
+        var n = +value;
+        if (!isToNumber) {
+          if (n == 1.234567890) {
+            n = 1;
+          }
+          else if (n == Infinity) {
+            n = MAX_INTEGER;
+          }
+          else if (n == Number.MIN_VALUE || n !== n) {
+            n = 0;
+          }
+          if (isToLength || isToSafeInteger) {
+            n = Math.min(n, isToLength ? MAX_ARRAY_LENGTH : MAX_SAFE_INTEGER);
+          }
+        }
+        var neg = isToLength ? 0 : -n;
+        return [n, n, n, n, n, n, neg, neg];
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        return lodashStable.flatMap(transforms, function(mod) {
+          return [func(mod(value)), func(Object(mod(value)))];
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert binary/octal strings to numbers', function(assert) {
+      assert.expect(1);
+
+      var numbers = [42, 5349, 1715004],
+          transforms = [identity, pad],
+          values = ['0b101010', '0o12345', '0x1a2b3c'];
+
+      var expected = lodashStable.map(numbers, function(n) {
+        return lodashStable.times(8, lodashStable.constant(n));
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        var upper = value.toUpperCase();
+        return lodashStable.flatMap(transforms, function(mod) {
+          return [func(mod(value)), func(Object(mod(value))), func(mod(upper)), func(Object(mod(upper)))];
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert invalid binary/octal strings to `' + (isToNumber ? 'NaN' : '0') + '`', function(assert) {
+      assert.expect(1);
+
+      var transforms = [identity, pad, positive, negative],
+          values = ['0b', '0o', '0x', '0b1010102', '0o123458', '0x1a2b3x'];
+
+      var expected = lodashStable.map(values, function(n) {
+        return lodashStable.times(8, lodashStable.constant(isToNumber ? NaN : 0));
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        return lodashStable.flatMap(transforms, function(mod) {
+          return [func(mod(value)), func(Object(mod(value)))];
+        });
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert symbols to `' + (isToNumber ? 'NaN' : '0') + '`', function(assert) {
+      assert.expect(1);
+
+      if (Symbol) {
+        var object1 = Object(symbol),
+            object2 = Object(symbol),
+            values = [symbol, object1, object2],
+            expected = lodashStable.map(values, lodashStable.constant(isToNumber ? NaN : 0));
+
+        object2.valueOf = undefined;
+        var actual = lodashStable.map(values, func);
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should convert empty values to `0` or `NaN`', function(assert) {
+      assert.expect(1);
+
+      var values = falsey.concat(whitespace);
+
+      var expected = lodashStable.map(values, function(value) {
+        return (isToNumber && value !== whitespace) ? Number(value) : 0;
+      });
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? func(value) : func();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce objects to numbers', function(assert) {
+      assert.expect(1);
+
+      var values = [
+        {},
+        [],
+        [1],
+        [1, 2],
+        { 'valueOf': '1.1' },
+        { 'valueOf': '1.1', 'toString': lodashStable.constant('2.2') },
+        { 'valueOf': lodashStable.constant('1.1'), 'toString': '2.2' },
+        { 'valueOf': lodashStable.constant('1.1'), 'toString': lodashStable.constant('2.2') },
+        { 'valueOf': lodashStable.constant('-0x1a2b3c') },
+        { 'toString': lodashStable.constant('-0x1a2b3c') },
+        { 'valueOf': lodashStable.constant('0o12345') },
+        { 'toString': lodashStable.constant('0o12345') },
+        { 'valueOf': lodashStable.constant('0b101010') },
+        { 'toString': lodashStable.constant('0b101010') }
+      ];
+
+      var expected = [
+        NaN,   0,   1,   NaN,
+        NaN,  2.2,  1.1, 1.1,
+        NaN,  NaN,
+        5349, 5349,
+        42,   42
+      ];
+
+      if (!isToNumber) {
+        expected = [
+          0, 0, 1, 0,
+          0, 2, 1, 1,
+          0, 0,
+          5349, 5349,
+          42, 42
+        ];
+      }
+      var actual = lodashStable.map(values, func);
+
+      assert.deepEqual(actual, expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toPairs');
+
+  (function() {
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.entries, _.toPairs);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toPairsIn');
+
+  (function() {
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.entriesIn, _.toPairsIn);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('toPairs methods');
+
+  lodashStable.each(['toPairs', 'toPairsIn'], function(methodName) {
+    var func = _[methodName],
+        isToPairs = methodName == 'toPairs';
+
+    QUnit.test('`_.' + methodName + '` should create an array of string keyed-value pairs', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2 },
+          actual = lodashStable.sortBy(func(object), 0);
+
+      assert.deepEqual(actual, [['a', 1], ['b', 2]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with an object that has a `length` property', function(assert) {
+      assert.expect(1);
+
+      var object = { '0': 'a', '1': 'b', 'length': 2 },
+          actual = lodashStable.sortBy(func(object), 0);
+
+      assert.deepEqual(actual, [['0', 'a'], ['1', 'b'], ['length', 2]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isToPairs ? 'not ' : '') + 'include inherited string keyed property values', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var expected = isToPairs ? [['a', 1]] : [['a', 1], ['b', 2]],
+          actual = lodashStable.sortBy(func(new Foo), 0);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with strings', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each(['xo', Object('xo')], function(string) {
+        var actual = lodashStable.sortBy(func(string), 0);
+        assert.deepEqual(actual, [['0', 'x'], ['1', 'o']]);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toPath');
+
+  (function() {
+    QUnit.test('should convert a string to a path', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.toPath('a.b.c'), ['a', 'b', 'c']);
+      assert.deepEqual(_.toPath('a[0].b.c'), ['a', '0', 'b', 'c']);
+    });
+
+    QUnit.test('should coerce array elements to strings', function(assert) {
+      assert.expect(4);
+
+      var array = ['a', 'b', 'c'];
+
+      lodashStable.each([array, lodashStable.map(array, Object)], function(value) {
+        var actual = _.toPath(value);
+        assert.deepEqual(actual, array);
+        assert.notStrictEqual(actual, array);
+      });
+    });
+
+    QUnit.test('should a new path array', function(assert) {
+      assert.expect(1);
+
+      assert.notStrictEqual(_.toPath('a.b.c'), _.toPath('a.b.c'));
+    });
+
+    QUnit.test('should not coerce symbols to strings', function(assert) {
+      assert.expect(4);
+
+      if (Symbol) {
+        var object = Object(symbol);
+        lodashStable.each([symbol, object, [symbol], [object]], function(value) {
+          var actual = _.toPath(value);
+          assert.ok(lodashStable.isSymbol(actual[0]));
+        });
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should handle complex paths', function(assert) {
+      assert.expect(1);
+
+      var actual = _.toPath('a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g');
+      assert.deepEqual(actual, ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g']);
+    });
+
+    QUnit.test('should ignore consecutive brackets and dots', function(assert) {
+      assert.expect(4);
+
+      var expected = ['a'];
+      assert.deepEqual(_.toPath('a.'), expected);
+      assert.deepEqual(_.toPath('a[]'), expected);
+
+      expected = ['a', 'b'];
+      assert.deepEqual(_.toPath('a..b'), expected);
+      assert.deepEqual(_.toPath('a[][]b'), expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toPlainObject');
+
+  (function() {
+    var args = arguments;
+
+    QUnit.test('should flatten inherited string keyed properties', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.b = 2;
+      }
+      Foo.prototype.c = 3;
+
+      var actual = lodashStable.assign({ 'a': 1 }, _.toPlainObject(new Foo));
+      assert.deepEqual(actual, { 'a': 1, 'b': 2, 'c': 3 });
+    });
+
+    QUnit.test('should convert `arguments` objects to plain objects', function(assert) {
+      assert.expect(1);
+
+      var actual = _.toPlainObject(args),
+          expected = { '0': 1, '1': 2, '2': 3 };
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should convert arrays to plain objects', function(assert) {
+      assert.expect(1);
+
+      var actual = _.toPlainObject(['a', 'b', 'c']),
+          expected = { '0': 'a', '1': 'b', '2': 'c' };
+
+      assert.deepEqual(actual, expected);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.toString');
+
+  (function() {
+    QUnit.test('should treat nullish values as empty strings', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysEmptyString);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.toString(value) : _.toString();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var values = [-0, Object(-0), 0, Object(0)],
+          expected = ['-0', '-0', '0', '0'],
+          actual = lodashStable.map(values, _.toString);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not error on symbols', function(assert) {
+      assert.expect(1);
+
+      if (Symbol) {
+        try {
+          assert.strictEqual(_.toString(symbol), 'Symbol(a)');
+        } catch (e) {
+          assert.ok(false, e.message);
+        }
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should return the `toString` result of the wrapped value', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _([1, 2, 3]);
+        assert.strictEqual(wrapped.toString(), '1,2,3');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.transform');
+
+  (function() {
+    function Foo() {
+      this.a = 1;
+      this.b = 2;
+      this.c = 3;
+    }
+
+    QUnit.test('should create an object with the same `[[Prototype]]` as `object` when `accumulator` is nullish', function(assert) {
+      assert.expect(4);
+
+      var accumulators = [, null, undefined],
+          object = new Foo,
+          expected = lodashStable.map(accumulators, alwaysTrue);
+
+      var iteratee = function(result, value, key) {
+        result[key] = square(value);
+      };
+
+      var mapper = function(accumulator, index) {
+        return index ? _.transform(object, iteratee, accumulator) : _.transform(object, iteratee);
+      };
+
+      var results = lodashStable.map(accumulators, mapper);
+
+      var actual = lodashStable.map(results, function(result) {
+        return result instanceof Foo;
+      });
+
+      assert.deepEqual(actual, expected);
+
+      expected = lodashStable.map(accumulators, lodashStable.constant({ 'a': 1, 'b': 4, 'c': 9 }));
+      actual = lodashStable.map(results, lodashStable.toPlainObject);
+
+      assert.deepEqual(actual, expected);
+
+      object = { 'a': 1, 'b': 2, 'c': 3 };
+      actual = lodashStable.map(accumulators, mapper);
+
+      assert.deepEqual(actual, expected);
+
+      object = [1, 2, 3];
+      expected = lodashStable.map(accumulators, lodashStable.constant([1, 4, 9]));
+      actual = lodashStable.map(accumulators, mapper);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should create regular arrays from typed arrays', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(typedArrays, alwaysTrue);
+
+      var actual = lodashStable.map(typedArrays, function(type) {
+        var Ctor = root[type],
+            array = Ctor ? new Ctor(new ArrayBuffer(24)) : [];
+
+        return lodashStable.isArray(_.transform(array, noop));
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should support an `accumulator` value', function(assert) {
+      assert.expect(6);
+
+      var values = [new Foo, [1, 2, 3], { 'a': 1, 'b': 2, 'c': 3 }],
+          expected = lodashStable.map(values, lodashStable.constant([1, 4, 9]));
+
+      var actual = lodashStable.map(values, function(value) {
+        return _.transform(value, function(result, value) {
+          result.push(square(value));
+        }, []);
+      });
+
+      assert.deepEqual(actual, expected);
+
+      var object = { 'a': 1, 'b': 4, 'c': 9 },
+      expected = [object, { '0': 1, '1': 4, '2': 9 }, object];
+
+      actual = lodashStable.map(values, function(value) {
+        return _.transform(value, function(result, value, key) {
+          result[key] = square(value);
+        }, {});
+      });
+
+      assert.deepEqual(actual, expected);
+
+      lodashStable.each([[], {}], function(accumulator) {
+        var actual = lodashStable.map(values, function(value) {
+          return _.transform(value, noop, accumulator);
+        });
+
+        assert.ok(lodashStable.every(actual, function(result) {
+          return result === accumulator;
+        }));
+
+        assert.strictEqual(_.transform(null, null, accumulator), accumulator);
+      });
+    });
+
+    QUnit.test('should treat sparse arrays as dense', function(assert) {
+      assert.expect(1);
+
+      var actual = _.transform(Array(1), function(result, value, index) {
+        result[index] = String(value);
+      });
+
+      assert.deepEqual(actual, ['undefined']);
+    });
+
+    QUnit.test('should work without an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      assert.ok(_.transform(new Foo) instanceof Foo);
+    });
+
+    QUnit.test('should ensure `object` is an object before using its `[[Prototype]]`', function(assert) {
+      assert.expect(2);
+
+      var Ctors = [Boolean, Boolean, Number, Number, Number, String, String],
+          values = [false, true, 0, 1, NaN, '', 'a'],
+          expected = lodashStable.map(values, alwaysEmptyObject);
+
+      var results = lodashStable.map(values, function(value) {
+        return _.transform(value);
+      });
+
+      assert.deepEqual(results, expected);
+
+      expected = lodashStable.map(values, alwaysFalse);
+
+      var actual = lodashStable.map(results, function(value, index) {
+        return value instanceof Ctors[index];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should ensure `object` constructor is a function before using its `[[Prototype]]`', function(assert) {
+      assert.expect(1);
+
+      Foo.prototype.constructor = null;
+      assert.notOk(_.transform(new Foo) instanceof Foo);
+      Foo.prototype.constructor = Foo;
+    });
+
+    QUnit.test('should create an empty object when given a falsey `object` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyObject);
+
+      var actual = lodashStable.map(falsey, function(object, index) {
+        return index ? _.transform(object) : _.transform();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    lodashStable.each({
+      'array': [1, 2, 3],
+      'object': { 'a': 1, 'b': 2, 'c': 3 }
+    },
+    function(object, key) {
+      QUnit.test('should provide the correct `iteratee` arguments when transforming an ' + key, function(assert) {
+        assert.expect(2);
+
+        var args;
+
+        _.transform(object, function() {
+          args || (args = slice.call(arguments));
+        });
+
+        var first = args[0];
+        if (key == 'array') {
+          assert.ok(first !== object && lodashStable.isArray(first));
+          assert.deepEqual(args, [first, 1, 0, object]);
+        } else {
+          assert.ok(first !== object && lodashStable.isPlainObject(first));
+          assert.deepEqual(args, [first, 1, 'a', object]);
+        }
+      });
+    });
+
+    QUnit.test('should create an object from the same realm as `object`', function(assert) {
+      assert.expect(1);
+
+      var objects = lodashStable.filter(realm, function(value) {
+        return lodashStable.isObject(value) && !lodashStable.isElement(value);
+      });
+
+      var expected = lodashStable.map(objects, alwaysTrue);
+
+      var actual = lodashStable.map(objects, function(object) {
+        var Ctor = object.constructor,
+            result = _.transform(object);
+
+        if (result === object) {
+          return false;
+        }
+        if (lodashStable.isTypedArray(object)) {
+          return result instanceof Array;
+        }
+        return result instanceof Ctor || !(new Ctor instanceof Ctor);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('trim methods');
+
+  lodashStable.each(['trim', 'trimStart', 'trimEnd'], function(methodName, index) {
+    var func = _[methodName],
+        parts = [];
+
+    if (index != 2) {
+      parts.push('leading');
+    }
+    if (index != 1) {
+      parts.push('trailing');
+    }
+    parts = parts.join(' and ');
+
+    QUnit.test('`_.' + methodName + '` should remove ' + parts + ' whitespace', function(assert) {
+      assert.expect(1);
+
+      var string = whitespace + 'a b c' + whitespace,
+          expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : '');
+
+      assert.strictEqual(func(string), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `string` to a string', function(assert) {
+      assert.expect(1);
+
+      var object = { 'toString': lodashStable.constant(whitespace + 'a b c' + whitespace) },
+          expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : '');
+
+      assert.strictEqual(func(object), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should remove ' + parts + ' `chars`', function(assert) {
+      assert.expect(1);
+
+      var string = '-_-a-b-c-_-',
+          expected = (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : '');
+
+      assert.strictEqual(func(string, '_-'), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should coerce `chars` to a string', function(assert) {
+      assert.expect(1);
+
+      var object = { 'toString': lodashStable.constant('_-') },
+          string = '-_-a-b-c-_-',
+          expected = (index == 2 ? '-_-' : '') + 'a-b-c' + (index == 1 ? '-_-' : '');
+
+      assert.strictEqual(func(string, object), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an empty string for empty values and `chars`', function(assert) {
+      assert.expect(6);
+
+      lodashStable.each([null, '_-'], function(chars) {
+        assert.strictEqual(func(null, chars), '');
+        assert.strictEqual(func(undefined, chars), '');
+        assert.strictEqual(func('', chars), '');
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `undefined` or empty string values for `chars`', function(assert) {
+      assert.expect(2);
+
+      var string = whitespace + 'a b c' + whitespace,
+          expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : '');
+
+      assert.strictEqual(func(string, undefined), expected);
+      assert.strictEqual(func(string, ''), string);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var string = Object(whitespace + 'a b c' + whitespace),
+          trimmed = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : ''),
+          actual = lodashStable.map([string, string, string], func);
+
+      assert.deepEqual(actual, [trimmed, trimmed, trimmed]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an unwrapped value when implicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var string = whitespace + 'a b c' + whitespace,
+            expected = (index == 2 ? whitespace : '') + 'a b c' + (index == 1 ? whitespace : '');
+
+        assert.strictEqual(_(string)[methodName](), expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var string = whitespace + 'a b c' + whitespace;
+        assert.ok(_(string).chain()[methodName]() instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('uncommon symbols');
+
+  (function() {
+    var flag = '\ud83c\uddfa\ud83c\uddf8',
+        heart = '\u2764' + emojiVar,
+        hearts = '\ud83d\udc95',
+        comboGlyph = '\ud83d\udc68\u200d' + heart + '\u200d\ud83d\udc8B\u200d\ud83d\udc68',
+        hashKeycap = '#' + emojiVar + '\u20e3',
+        leafs = '\ud83c\udf42',
+        mic = '\ud83c\udf99',
+        noMic = mic + '\u20e0',
+        raisedHand = '\u270B' + emojiVar,
+        rocket = '\ud83d\ude80',
+        thumbsUp = '\ud83d\udc4d';
+
+    QUnit.test('should account for astral symbols', function(assert) {
+      assert.expect(34);
+
+      var allHearts = _.repeat(hearts, 10),
+          chars = hearts + comboGlyph,
+          string = 'A ' + leafs + ', ' + comboGlyph + ', and ' + rocket,
+          trimChars = comboGlyph + hearts,
+          trimString = trimChars + string + trimChars;
+
+      assert.strictEqual(_.camelCase(hearts + ' the ' + leafs), hearts + 'The' + leafs);
+      assert.strictEqual(_.camelCase(string), 'a' + leafs + comboGlyph + 'And' + rocket);
+      assert.strictEqual(_.capitalize(rocket), rocket);
+
+      assert.strictEqual(_.pad(string, 16), ' ' + string + '  ');
+      assert.strictEqual(_.padStart(string, 16), '   ' + string);
+      assert.strictEqual(_.padEnd(string, 16), string + '   ');
+
+      assert.strictEqual(_.pad(string, 16, chars), hearts + string + chars);
+      assert.strictEqual(_.padStart(string, 16, chars), chars + hearts + string);
+      assert.strictEqual(_.padEnd(string, 16, chars), string + chars + hearts);
+
+      assert.strictEqual(_.size(string), 13);
+      assert.deepEqual(_.split(string, ' '), ['A', leafs + ',', comboGlyph + ',', 'and', rocket]);
+      assert.deepEqual(_.split(string, ' ', 3), ['A', leafs + ',', comboGlyph + ',']);
+      assert.deepEqual(_.split(string, undefined), [string]);
+      assert.deepEqual(_.split(string, undefined, -1), [string]);
+      assert.deepEqual(_.split(string, undefined, 0), []);
+
+      var expected = ['A', ' ', leafs, ',', ' ', comboGlyph, ',', ' ', 'a', 'n', 'd', ' ', rocket];
+
+      assert.deepEqual(_.split(string, ''), expected);
+      assert.deepEqual(_.split(string, '', 6), expected.slice(0, 6));
+      assert.deepEqual(_.toArray(string), expected);
+
+      assert.strictEqual(_.trim(trimString, chars), string);
+      assert.strictEqual(_.trimStart(trimString, chars), string + trimChars);
+      assert.strictEqual(_.trimEnd(trimString, chars), trimChars + string);
+
+      assert.strictEqual(_.truncate(string, { 'length': 13 }), string);
+      assert.strictEqual(_.truncate(string, { 'length': 6 }), 'A ' + leafs + '...');
+
+      assert.deepEqual(_.words(string), ['A', leafs, comboGlyph, 'and', rocket]);
+      assert.deepEqual(_.toArray(hashKeycap), [hashKeycap]);
+      assert.deepEqual(_.toArray(noMic), [noMic]);
+
+      lodashStable.times(2, function(index) {
+        var separator = index ? RegExp(hearts) : hearts,
+            options = { 'length': 4, 'separator': separator },
+            actual = _.truncate(string, options);
+
+        assert.strictEqual(actual, 'A...');
+        assert.strictEqual(actual.length, 4);
+
+        actual = _.truncate(allHearts, options);
+        assert.strictEqual(actual, hearts + '...');
+        assert.strictEqual(actual.length, 5);
+      });
+    });
+
+    QUnit.test('should account for combining diacritical marks', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.map(comboMarks, function(mark) {
+        return 'o' + mark;
+      });
+
+      var expected = lodashStable.map(values, function(value) {
+        return [1, [value], [value]];
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        return [_.size(value), _.toArray(value), _.words(value)];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should account for fitzpatrick modifiers', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.map(fitzModifiers, function(modifier) {
+        return thumbsUp + modifier;
+      });
+
+      var expected = lodashStable.map(values, function(value) {
+        return [1, [value], [value]];
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        return [_.size(value), _.toArray(value), _.words(value)];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should account for regional symbols', function(assert) {
+      assert.expect(6);
+
+      var pair = flag.match(/\ud83c[\udde6-\uddff]/g),
+          regionals = pair.join(' ');
+
+      assert.strictEqual(_.size(flag), 1);
+      assert.strictEqual(_.size(regionals), 3);
+
+      assert.deepEqual(_.toArray(flag), [flag]);
+      assert.deepEqual(_.toArray(regionals), [pair[0], ' ', pair[1]]);
+
+      assert.deepEqual(_.words(flag), [flag]);
+      assert.deepEqual(_.words(regionals), [pair[0], pair[1]]);
+    });
+
+    QUnit.test('should account for variation selectors', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.size(heart), 1);
+      assert.deepEqual(_.toArray(heart), [heart]);
+      assert.deepEqual(_.words(heart), [heart]);
+    });
+
+    QUnit.test('should account for variation selectors with fitzpatrick modifiers', function(assert) {
+      assert.expect(1);
+
+      var values = lodashStable.map(fitzModifiers, function(modifier) {
+        return raisedHand + modifier;
+      });
+
+      var expected = lodashStable.map(values, function(value) {
+        return [1, [value], [value]];
+      });
+
+      var actual = lodashStable.map(values, function(value) {
+        return [_.size(value), _.toArray(value), _.words(value)];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should match lone surrogates', function(assert) {
+      assert.expect(3);
+
+      var pair = hearts.split(''),
+          surrogates = pair[0] + ' ' + pair[1];
+
+      assert.strictEqual(_.size(surrogates), 3);
+      assert.deepEqual(_.toArray(surrogates), [pair[0], ' ', pair[1]]);
+      assert.deepEqual(_.words(surrogates), []);
+    });
+
+    QUnit.test('should match side by side fitzpatrick modifiers separately ', function(assert) {
+      assert.expect(1);
+
+      var string = fitzModifiers[0] + fitzModifiers[0];
+      assert.deepEqual(_.toArray(string), [fitzModifiers[0], fitzModifiers[0]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unary');
+
+  (function() {
+    function fn() {
+      return slice.call(arguments);
+    }
+
+    QUnit.test('should cap the number of arguments provided to `func`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(['6', '8', '10'], _.unary(parseInt));
+      assert.deepEqual(actual, [6, 8, 10]);
+    });
+
+    QUnit.test('should work when provided less than the capped number of arguments', function(assert) {
+      assert.expect(1);
+
+      var capped = _.unary(fn);
+      assert.deepEqual(capped(), []);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unescape');
+
+  (function() {
+    var escaped = '&amp;&lt;&gt;&quot;&#39;\/',
+        unescaped = '&<>"\'\/';
+
+    escaped += escaped;
+    unescaped += unescaped;
+
+    QUnit.test('should unescape entities in order', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.unescape('&amp;lt;'), '&lt;');
+    });
+
+    QUnit.test('should unescape the proper entities', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.unescape(escaped), unescaped);
+    });
+
+    QUnit.test('should not unescape the "&#x2F;" entity', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.unescape('&#x2F;'), '&#x2F;');
+    });
+
+    QUnit.test('should handle strings with nothing to unescape', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.unescape('abc'), 'abc');
+    });
+
+    QUnit.test('should unescape the same characters escaped by `_.escape`', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(_.unescape(_.escape(unescaped)), unescaped);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unionBy');
+
+  (function() {
+    QUnit.test('should accept an `iteratee` argument', function(assert) {
+      assert.expect(2);
+
+      var actual = _.unionBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+      assert.deepEqual(actual, [2.1, 1.2, 4.3]);
+
+      actual = _.unionBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+      assert.deepEqual(actual, [{ 'x': 1 }, { 'x': 2 }]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.unionBy([2.1, 1.2], [4.3, 2.4], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [2.1]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unionWith');
+
+  (function() {
+    QUnit.test('should work with a `comparator` argument', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }],
+          others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }],
+          actual = _.unionWith(objects, others, lodashStable.isEqual);
+
+      assert.deepEqual(actual, [objects[0], objects[1], others[0]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('union methods');
+
+  lodashStable.each(['union', 'unionBy', 'unionWith'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return the union of the given arrays', function(assert) {
+      assert.expect(1);
+
+      var actual = func([1, 3, 2], [5, 2, 1, 4], [2, 1]);
+      assert.deepEqual(actual, [1, 3, 2, 5, 4]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should not flatten nested arrays', function(assert) {
+      assert.expect(1);
+
+      var actual = func([1, 3, 2], [1, [5]], [2, [4]]);
+      assert.deepEqual(actual, [1, 3, 2, [5], [4]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore values that are not arrays or `arguments` objects', function(assert) {
+      assert.expect(3);
+
+      var array = [0];
+      assert.deepEqual(func(array, 3, { '0': 1 }, null), array);
+      assert.deepEqual(func(null, array, null, [2, 1]), [0, 2, 1]);
+      assert.deepEqual(func(array, null, args, null), [0, 1, 2, 3]);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.uniq');
+
+  (function() {
+    QUnit.test('should perform an unsorted uniq when used as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var array = [[2, 1, 2], [1, 2, 1]],
+          actual = lodashStable.map(array, lodashStable.uniq);
+
+      assert.deepEqual(actual, [[2, 1], [1, 2]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('uniqBy methods');
+
+  lodashStable.each(['uniqBy', 'sortedUniqBy'], function(methodName) {
+    var func = _[methodName],
+        isSorted = methodName == 'sortedUniqBy',
+        objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }];
+
+    if (isSorted) {
+      objects = _.sortBy(objects, 'a');
+    }
+    QUnit.test('`_.' + methodName + '` should work with an `iteratee` argument', function(assert) {
+      assert.expect(1);
+
+      var expected = isSorted ? [{ 'a': 1 }, { 'a': 2 }, { 'a': 3 }] : objects.slice(0, 3);
+
+      var actual = func(objects, function(object) {
+        return object.a;
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should work with large arrays', function(assert) {
+      assert.expect(2);
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function() {
+        return [1, 2];
+      });
+
+      var actual = func(largeArray, String);
+
+      assert.deepEqual(actual, [[1, 2]]);
+      assert.strictEqual(actual[0], largeArray[0]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      func(objects, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [objects[0]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `_.property` shorthands', function(assert) {
+      assert.expect(2);
+
+      var expected = isSorted ? [{ 'a': 1 }, { 'a': 2 }, { 'a': 3 }] : objects.slice(0, 3),
+          actual = func(objects, 'a');
+
+      assert.deepEqual(actual, expected);
+
+      var arrays = [[2], [3], [1], [2], [3], [1]];
+      if (isSorted) {
+        arrays = lodashStable.sortBy(arrays, 0);
+      }
+      expected = isSorted ? [[1], [2], [3]] : arrays.slice(0, 3);
+      actual = func(arrays, 0);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    lodashStable.each({
+      'an array': [0, 'a'],
+      'an object': { '0': 'a' },
+      'a number': 0,
+      'a string': '0'
+    },
+    function(iteratee, key) {
+      QUnit.test('`_.' + methodName + '` should work with ' + key + ' for `iteratee`', function(assert) {
+        assert.expect(1);
+
+        var actual = func([['a'], ['a'], ['b']], iteratee);
+        assert.deepEqual(actual, [['a'], ['b']]);
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.uniqWith');
+
+  (function() {
+    QUnit.test('should work with a `comparator` argument', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 },  { 'x': 1, 'y': 2 }],
+          actual = _.uniqWith(objects, lodashStable.isEqual);
+
+      assert.deepEqual(actual, [objects[0], objects[1]]);
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function(index) {
+        return isEven(index) ? -0 : 0;
+      });
+
+      var arrays = [[-0, 0], largeArray],
+          expected = lodashStable.map(arrays, lodashStable.constant(['-0']));
+
+      var actual = lodashStable.map(arrays, function(array) {
+        return lodashStable.map(_.uniqWith(array, lodashStable.eq), lodashStable.toString);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('uniq methods');
+
+  lodashStable.each(['uniq', 'uniqBy', 'uniqWith', 'sortedUniq', 'sortedUniqBy'], function(methodName) {
+    var func = _[methodName],
+        isSorted = /^sorted/.test(methodName),
+        objects = [{ 'a': 2 }, { 'a': 3 }, { 'a': 1 }, { 'a': 2 }, { 'a': 3 }, { 'a': 1 }];
+
+    if (isSorted) {
+      objects = _.sortBy(objects, 'a');
+    }
+    else {
+      QUnit.test('`_.' + methodName + '` should return unique values of an unsorted array', function(assert) {
+        assert.expect(1);
+
+        var array = [2, 3, 1, 2, 3, 1];
+        assert.deepEqual(func(array), [2, 3, 1]);
+      });
+    }
+    QUnit.test('`_.' + methodName + '` should return unique values of a sorted array', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 1, 2, 2, 3];
+      assert.deepEqual(func(array), [1, 2, 3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat object instances as unique', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(objects), objects);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat `-0` as `0`', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.map(func([-0, 0]), lodashStable.toString);
+      assert.deepEqual(actual, ['0']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should match `NaN`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func([NaN, NaN]), [NaN]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays', function(assert) {
+      assert.expect(1);
+
+      var largeArray = [],
+          expected = [0, {}, 'a'],
+          count = Math.ceil(LARGE_ARRAY_SIZE / expected.length);
+
+      lodashStable.each(expected, function(value) {
+        lodashStable.times(count, function() {
+          largeArray.push(value);
+        });
+      });
+
+      assert.deepEqual(func(largeArray), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of `-0` as `0`', function(assert) {
+      assert.expect(1);
+
+      var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, function(index) {
+        return isEven(index) ? -0 : 0;
+      });
+
+      var actual = lodashStable.map(func(largeArray), lodashStable.toString);
+      assert.deepEqual(actual, ['0']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of boolean, `NaN`, and nullish values', function(assert) {
+      assert.expect(1);
+
+      var largeArray = [],
+          expected = [null, undefined, false, true, NaN],
+          count = Math.ceil(LARGE_ARRAY_SIZE / expected.length);
+
+      lodashStable.each(expected, function(value) {
+        lodashStable.times(count, function() {
+          largeArray.push(value);
+        });
+      });
+
+      assert.deepEqual(func(largeArray), expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of symbols', function(assert) {
+      assert.expect(1);
+
+      if (Symbol) {
+        var largeArray = lodashStable.times(LARGE_ARRAY_SIZE, Symbol);
+        assert.deepEqual(func(largeArray), largeArray);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with large arrays of well-known symbols', function(assert) {
+      assert.expect(1);
+
+      // See http://www.ecma-international.org/ecma-262/6.0/#sec-well-known-symbols.
+      if (Symbol) {
+        var expected = [
+          Symbol.hasInstance, Symbol.isConcatSpreadable, Symbol.iterator,
+          Symbol.match, Symbol.replace, Symbol.search, Symbol.species,
+          Symbol.split, Symbol.toPrimitive, Symbol.toStringTag, Symbol.unscopables
+        ];
+
+        var largeArray = [],
+            count = Math.ceil(LARGE_ARRAY_SIZE / expected.length);
+
+        expected = lodashStable.map(expected, function(symbol) {
+          return symbol || {};
+        });
+
+        lodashStable.each(expected, function(value) {
+          lodashStable.times(count, function() {
+            largeArray.push(value);
+          });
+        });
+
+        assert.deepEqual(func(largeArray), expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should distinguish between numbers and numeric strings', function(assert) {
+      assert.expect(1);
+
+      var largeArray = [],
+          expected = ['2', 2, Object('2'), Object(2)],
+          count = Math.ceil(LARGE_ARRAY_SIZE / expected.length);
+
+      lodashStable.each(expected, function(value) {
+        lodashStable.times(count, function() {
+          largeArray.push(value);
+        });
+      });
+
+      assert.deepEqual(func(largeArray), expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.uniqueId');
+
+  (function() {
+    QUnit.test('should generate unique ids', function(assert) {
+      assert.expect(1);
+
+      var actual = lodashStable.times(1000, function(assert) {
+        return _.uniqueId();
+      });
+
+      assert.strictEqual(lodashStable.uniq(actual).length, actual.length);
+    });
+
+    QUnit.test('should return a string value when not providing a prefix argument', function(assert) {
+      assert.expect(1);
+
+      assert.strictEqual(typeof _.uniqueId(), 'string');
+    });
+
+    QUnit.test('should coerce the prefix argument to a string', function(assert) {
+      assert.expect(1);
+
+      var actual = [_.uniqueId(3), _.uniqueId(2), _.uniqueId(1)];
+      assert.ok(/3\d+,2\d+,1\d+/.test(actual));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unset');
+
+  (function() {
+    QUnit.test('should unset property values', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a', ['a']], function(path) {
+        var object = { 'a': 1, 'c': 2 };
+        assert.strictEqual(_.unset(object, path), true);
+        assert.deepEqual(object, { 'c': 2 });
+      });
+    });
+
+    QUnit.test('should preserve the sign of `0`', function(assert) {
+      assert.expect(1);
+
+      var props = [-0, Object(-0), 0, Object(0)],
+          expected = lodashStable.map(props, lodashStable.constant([true, false]));
+
+      var actual = lodashStable.map(props, function(key) {
+        var object = { '-0': 'a', '0': 'b' };
+        return [_.unset(object, key), lodashStable.toString(key) in object];
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should unset symbol keyed property values', function(assert) {
+      assert.expect(2);
+
+      if (Symbol) {
+        var object = {};
+        object[symbol] = 1;
+
+        assert.strictEqual(_.unset(object, symbol), true);
+        assert.notOk(symbol in object);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should unset deep property values', function(assert) {
+      assert.expect(4);
+
+      lodashStable.each(['a.b', ['a', 'b']], function(path) {
+        var object = { 'a': { 'b': null } };
+        assert.strictEqual(_.unset(object, path), true);
+        assert.deepEqual(object, { 'a': {} });
+      });
+    });
+
+    QUnit.test('should handle complex paths', function(assert) {
+      assert.expect(4);
+
+      var paths = [
+        'a[-1.23]["[\\"b\\"]"].c[\'[\\\'d\\\']\'][\ne\n][f].g',
+        ['a', '-1.23', '["b"]', 'c', "['d']", '\ne\n', 'f', 'g']
+      ];
+
+      lodashStable.each(paths, function(path) {
+        var object = { 'a': { '-1.23': { '["b"]': { 'c': { "['d']": { '\ne\n': { 'f': { 'g': 8 } } } } } } } };
+        assert.strictEqual(_.unset(object, path), true);
+        assert.notOk('g' in object.a[-1.23]['["b"]'].c["['d']"]['\ne\n'].f);
+      });
+    });
+
+    QUnit.test('should return `true` for nonexistent paths', function(assert) {
+      assert.expect(5);
+
+      var object = { 'a': { 'b': { 'c': null } } };
+
+      lodashStable.each(['z', 'a.z', 'a.b.z', 'a.b.c.z'], function(path) {
+        assert.strictEqual(_.unset(object, path), true);
+      });
+
+      assert.deepEqual(object, { 'a': { 'b': { 'c': null } } });
+    });
+
+    QUnit.test('should not error when `object` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [null, undefined],
+          expected = [[true, true], [true, true]];
+
+      var actual = lodashStable.map(values, function(value) {
+        try {
+          return [_.unset(value, 'a.b'), _.unset(value, ['a', 'b'])];
+        } catch (e) {
+          return e.message;
+        }
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should follow `path` over non-plain objects', function(assert) {
+      assert.expect(8);
+
+      var object = { 'a': '' },
+          paths = ['constructor.prototype.a', ['constructor', 'prototype', 'a']];
+
+      lodashStable.each(paths, function(path) {
+        numberProto.a = 1;
+
+        var actual = _.unset(0, path);
+        assert.strictEqual(actual, true);
+        assert.notOk('a' in numberProto);
+
+        delete numberProto.a;
+      });
+
+      lodashStable.each(['a.replace.b', ['a', 'replace', 'b']], function(path) {
+        stringProto.replace.b = 1;
+
+        var actual = _.unset(object, path);
+        assert.strictEqual(actual, true);
+        assert.notOk('a' in stringProto.replace);
+
+        delete stringProto.replace.b;
+      });
+    });
+
+    QUnit.test('should return `false` for non-configurable properties', function(assert) {
+      assert.expect(1);
+
+      var object = {};
+
+      if (!isStrict && defineProperty) {
+        defineProperty(object, 'a', {
+          'configurable': false,
+          'enumerable': true,
+          'writable': true,
+          'value': 1,
+        });
+        assert.strictEqual(_.unset(object, 'a'), false);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unzipWith');
+
+  (function() {
+    QUnit.test('should unzip arrays combining regrouped elements with `iteratee`', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 4], [2, 5], [3, 6]];
+
+      var actual = _.unzipWith(array, function(a, b, c) {
+        return a + b + c;
+      });
+
+      assert.deepEqual(actual, [6, 15]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.unzipWith([[1, 3, 5], [2, 4, 6]], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [1, 2]);
+    });
+
+    QUnit.test('should perform a basic unzip when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 3], [2, 4]],
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant(_.unzip(array)));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.unzipWith(array, value) : _.unzipWith(array);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.updateWith');
+
+  (function() {
+    QUnit.test('should work with a `customizer` callback', function(assert) {
+      assert.expect(1);
+
+      var actual = _.updateWith({ '0': {} }, '[0][1][2]', alwaysThree, function(value) {
+        return lodashStable.isObject(value) ? undefined : {};
+      });
+
+      assert.deepEqual(actual, { '0': { '1': { '2': 3 } } });
+    });
+
+    QUnit.test('should work with a `customizer` that returns `undefined`', function(assert) {
+      assert.expect(1);
+
+      var actual = _.updateWith({}, 'a[0].b.c', alwaysFour, noop);
+      assert.deepEqual(actual, { 'a': [{ 'b': { 'c': 4 } }] });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('update methods');
+
+  lodashStable.each(['update', 'updateWith'], function(methodName) {
+    var func = _[methodName],
+        oldValue = 1;
+
+    QUnit.test('`_.' + methodName + '` should invoke `updater` with the value on `path` of `object`', function(assert) {
+      assert.expect(4);
+
+      var object = { 'a': [{ 'b': { 'c': oldValue } }] },
+          expected = oldValue + 1;
+
+      lodashStable.each(['a[0].b.c', ['a', '0', 'b', 'c']], function(path) {
+        func(object, path, function(n) {
+          assert.strictEqual(n, oldValue);
+          return ++n;
+        });
+
+        assert.strictEqual(object.a[0].b.c, expected);
+        object.a[0].b.c = oldValue;
+      });
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.upperCase');
+
+  (function() {
+    QUnit.test('should uppercase as space-separated words', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.upperCase('--foo-bar--'), 'FOO BAR');
+      assert.strictEqual(_.upperCase('fooBar'), 'FOO BAR');
+      assert.strictEqual(_.upperCase('__foo_bar__'), 'FOO BAR');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.upperFirst');
+
+  (function() {
+    QUnit.test('should uppercase only the first character', function(assert) {
+      assert.expect(3);
+
+      assert.strictEqual(_.upperFirst('fred'), 'Fred');
+      assert.strictEqual(_.upperFirst('Fred'), 'Fred');
+      assert.strictEqual(_.upperFirst('FRED'), 'FRED');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('values methods');
+
+  lodashStable.each(['values', 'valuesIn'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        strictArgs = (function() { 'use strict'; return arguments; }(1, 2, 3)),
+        func = _[methodName],
+        isValues = methodName == 'values';
+
+    QUnit.test('`_.' + methodName + '` should get string keyed values of `object`', function(assert) {
+      assert.expect(1);
+
+      var object = { 'a': 1, 'b': 2 },
+          actual = func(object).sort();
+
+      assert.deepEqual(actual, [1, 2]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with an object that has a `length` property', function(assert) {
+      assert.expect(1);
+
+      var object = { '0': 'a', '1': 'b', 'length': 2 },
+          actual = func(object).sort();
+
+      assert.deepEqual(actual, [2, 'a', 'b']);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isValues ? 'not ' : '') + 'include inherited string keyed property values', function(assert) {
+      assert.expect(1);
+
+      function Foo() {
+        this.a = 1;
+      }
+      Foo.prototype.b = 2;
+
+      var expected = isValues ? [1] : [1, 2],
+          actual = func(new Foo).sort();
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      var values = [args, strictArgs],
+          expected = lodashStable.map(values, lodashStable.constant([1, 2, 3]));
+
+      var actual = lodashStable.map(values, function(value) {
+        return func(value).sort();
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.without');
+
+  (function() {
+    QUnit.test('should use strict equality to determine the values to reject', function(assert) {
+      assert.expect(2);
+
+      var object1 = { 'a': 1 },
+          object2 = { 'b': 2 },
+          array = [object1, object2];
+
+      assert.deepEqual(_.without(array, { 'a': 1 }), array);
+      assert.deepEqual(_.without(array, object1), [object2]);
+    });
+
+    QUnit.test('should remove all occurrences of each value from an array', function(assert) {
+      assert.expect(1);
+
+      var array = [1, 2, 3, 1, 2, 3];
+      assert.deepEqual(_.without(array, 1, 2), [3, 3]);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.words');
+
+  (function() {
+    QUnit.test('should treat latin-1 supplementary letters as words', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(burredLetters, function(letter) {
+        return [letter];
+      });
+
+      var actual = lodashStable.map(burredLetters, function(letter) {
+        return _.words(letter);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not treat mathematical operators as words', function(assert) {
+      assert.expect(1);
+
+      var operators = ['\xac', '\xb1', '\xd7', '\xf7'],
+          expected = lodashStable.map(operators, alwaysEmptyArray),
+          actual = lodashStable.map(operators, _.words);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not treat punctuation as words', function(assert) {
+      assert.expect(1);
+
+      var marks = [
+        '\u2012', '\u2013', '\u2014', '\u2015',
+        '\u2024', '\u2025', '\u2026',
+        '\u205d', '\u205e'
+      ];
+
+      var expected = lodashStable.map(marks, alwaysEmptyArray),
+          actual = lodashStable.map(marks, _.words);
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should support a `pattern` argument', function(assert) {
+      assert.expect(2);
+
+      assert.deepEqual(_.words('abcd', /ab|cd/g), ['ab', 'cd']);
+      assert.deepEqual(_.words('abcd', 'ab|cd'), ['ab']);
+    });
+
+    QUnit.test('should work with compound words', function(assert) {
+      assert.expect(12);
+
+      assert.deepEqual(_.words('12Feet'), ['12', 'Feet']);
+      assert.deepEqual(_.words('aeiouAreVowels'), ['aeiou', 'Are', 'Vowels']);
+      assert.deepEqual(_.words('enable 6h format'), ['enable', '6', 'h', 'format']);
+      assert.deepEqual(_.words('enable 24H format'), ['enable', '24', 'H', 'format']);
+      assert.deepEqual(_.words('isISO8601'), ['is', 'ISO', '8601']);
+      assert.deepEqual(_.words('LETTERSAeiouAreVowels'), ['LETTERS', 'Aeiou', 'Are', 'Vowels']);
+      assert.deepEqual(_.words('tooLegit2Quit'), ['too', 'Legit', '2', 'Quit']);
+      assert.deepEqual(_.words('walk500Miles'), ['walk', '500', 'Miles']);
+      assert.deepEqual(_.words('xhr2Request'), ['xhr', '2', 'Request']);
+      assert.deepEqual(_.words('XMLHttp'), ['XML', 'Http']);
+      assert.deepEqual(_.words('XmlHTTP'), ['Xml', 'HTTP']);
+      assert.deepEqual(_.words('XmlHttp'), ['Xml', 'Http']);
+    });
+
+    QUnit.test('should work with compound words containing diacritical marks', function(assert) {
+      assert.expect(3);
+
+      assert.deepEqual(_.words('LETTERSÆiouAreVowels'), ['LETTERS', 'Æiou', 'Are', 'Vowels']);
+      assert.deepEqual(_.words('æiouAreVowels'), ['æiou', 'Are', 'Vowels']);
+      assert.deepEqual(_.words('æiou2Consonants'), ['æiou', '2', 'Consonants']);
+    });
+
+    QUnit.test('should work with contractions', function(assert) {
+      assert.expect(2);
+
+      var postfixes = ['d', 'll', 'm', 're', 's', 't', 've'];
+
+      lodashStable.each(["'", '\u2019'], function(apos) {
+        var actual = lodashStable.map(postfixes, function(postfix) {
+          return _.words('a b' + apos + postfix +  ' c');
+        });
+
+        var expected = lodashStable.map(postfixes, function(postfix) {
+          return ['a', 'b' + apos + postfix, 'c'];
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+
+    QUnit.test('should work as an iteratee for methods like `_.map`', function(assert) {
+      assert.expect(1);
+
+      var strings = lodashStable.map(['a', 'b', 'c'], Object),
+          actual = lodashStable.map(strings, _.words);
+
+      assert.deepEqual(actual, [['a'], ['b'], ['c']]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.wrap');
+
+  (function() {
+    QUnit.test('should create a wrapped function', function(assert) {
+      assert.expect(1);
+
+      var p = _.wrap(_.escape, function(func, text) {
+        return '<p>' + func(text) + '</p>';
+      });
+
+      assert.strictEqual(p('fred, barney, & pebbles'), '<p>fred, barney, &amp; pebbles</p>');
+    });
+
+    QUnit.test('should provide the correct `wrapper` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      var wrapped = _.wrap(noop, function() {
+        args || (args = slice.call(arguments));
+      });
+
+      wrapped(1, 2, 3);
+      assert.deepEqual(args, [noop, 1, 2, 3]);
+    });
+
+    QUnit.test('should use `_.identity` when `wrapper` is nullish', function(assert) {
+      assert.expect(1);
+
+      var values = [, null, undefined],
+          expected = lodashStable.map(values, alwaysA);
+
+      var actual = lodashStable.map(values, function(value, index) {
+        var wrapped = index ? _.wrap('a', value) : _.wrap('a');
+        return wrapped('b', 'c');
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('should not set a `this` binding', function(assert) {
+      assert.expect(1);
+
+      var p = _.wrap(_.escape, function(func) {
+        return '<p>' + func(this.text) + '</p>';
+      });
+
+      var object = { 'p': p, 'text': 'fred, barney, & pebbles' };
+      assert.strictEqual(object.p(), '<p>fred, barney, &amp; pebbles</p>');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('xor methods');
+
+  lodashStable.each(['xor', 'xorBy', 'xorWith'], function(methodName) {
+    var args = (function() { return arguments; }(1, 2, 3)),
+        func = _[methodName];
+
+    QUnit.test('`_.' + methodName + '` should return the symmetric difference of the given arrays', function(assert) {
+      assert.expect(1);
+
+      var actual = func([1, 2, 5], [2, 3, 5], [3, 4, 5]);
+      assert.deepEqual(actual, [1, 4, 5]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return an array of unique values', function(assert) {
+      assert.expect(2);
+
+      var actual = func([1, 1, 2, 5], [2, 2, 3, 5], [3, 4, 5, 5]);
+      assert.deepEqual(actual, [1, 4, 5]);
+
+      actual = func([1, 1]);
+      assert.deepEqual(actual, [1]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a new array when a single array is given', function(assert) {
+      assert.expect(1);
+
+      var array = [1];
+      assert.notStrictEqual(func(array), array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore individual secondary arguments', function(assert) {
+      assert.expect(1);
+
+      var array = [0];
+      assert.deepEqual(func(array, 3, null, { '0': 1 }), array);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore values that are not arrays or `arguments` objects', function(assert) {
+      assert.expect(3);
+
+      var array = [1, 2];
+      assert.deepEqual(func(array, 3, { '0': 1 }, null), array);
+      assert.deepEqual(func(null, array, null, [2, 3]), [1, 3]);
+      assert.deepEqual(func(array, null, args, null), [3]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should return a wrapped value when chaining', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _([1, 2, 3])[methodName]([5, 2, 1, 4]);
+        assert.ok(wrapped instanceof _);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('`_.' + methodName + '` should work when in a lazy sequence before `head` or `last`', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE + 1),
+            wrapped = _(array).slice(1)[methodName]([LARGE_ARRAY_SIZE, LARGE_ARRAY_SIZE + 1]);
+
+        var actual = lodashStable.map(['head', 'last'], function(methodName) {
+          return wrapped[methodName]();
+        });
+
+        assert.deepEqual(actual, [1, LARGE_ARRAY_SIZE + 1]);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.xorBy');
+
+  (function() {
+    QUnit.test('should accept an `iteratee` argument', function(assert) {
+      assert.expect(2);
+
+      var actual = _.xorBy([2.1, 1.2], [4.3, 2.4], Math.floor);
+      assert.deepEqual(actual, [1.2, 4.3]);
+
+      actual = _.xorBy([{ 'x': 1 }], [{ 'x': 2 }, { 'x': 1 }], 'x');
+      assert.deepEqual(actual, [{ 'x': 2 }]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.xorBy([2.1, 1.2], [4.3, 2.4], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [4.3]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.xorWith');
+
+  (function() {
+    QUnit.test('should work with a `comparator` argument', function(assert) {
+      assert.expect(1);
+
+      var objects = [{ 'x': 1, 'y': 2 }, { 'x': 2, 'y': 1 }],
+          others = [{ 'x': 1, 'y': 1 }, { 'x': 1, 'y': 2 }],
+          actual = _.xorWith(objects, others, lodashStable.isEqual);
+
+      assert.deepEqual(actual, [objects[1], others[0]]);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('zipObject methods');
+
+  lodashStable.each(['zipObject', 'zipObjectDeep'], function(methodName) {
+    var func = _[methodName],
+        object = { 'barney': 36, 'fred': 40 },
+        isDeep = methodName == 'zipObjectDeep';
+
+    QUnit.test('`_.' + methodName + '` should zip together key/value arrays into an object', function(assert) {
+      assert.expect(1);
+
+      var actual = func(['barney', 'fred'], [36, 40]);
+      assert.deepEqual(actual, object);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore extra `values`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(['a'], [1, 2]), { 'a': 1 });
+    });
+
+    QUnit.test('`_.' + methodName + '` should assign `undefined` values for extra `keys`', function(assert) {
+      assert.expect(1);
+
+      assert.deepEqual(func(['a', 'b'], [1]), { 'a': 1, 'b': undefined });
+    });
+
+    QUnit.test('`_.' + methodName + '` should ' + (isDeep ? '' : 'not ') + 'support deep paths', function(assert) {
+      assert.expect(2);
+
+      lodashStable.each(['a.b.c', ['a', 'b', 'c']], function(path, index) {
+        var expected = isDeep ? ({ 'a': { 'b': { 'c': 1 } } }) : (index ? { 'a,b,c': 1 } : { 'a.b.c': 1 });
+        assert.deepEqual(func([path], [1]), expected);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should work in a lazy sequence', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var values = lodashStable.range(LARGE_ARRAY_SIZE),
+            props = lodashStable.map(values, function(value) { return 'key' + value; }),
+            actual = _(props)[methodName](values).map(square).filter(isEven).take().value();
+
+        assert.deepEqual(actual, _.take(_.filter(_.map(func(props, values), square), isEven)));
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.zipWith');
+
+  (function() {
+    QUnit.test('should zip arrays combining grouped elements with `iteratee`', function(assert) {
+      assert.expect(2);
+
+      var array1 = [1, 2, 3],
+          array2 = [4, 5, 6],
+          array3 = [7, 8, 9];
+
+      var actual = _.zipWith(array1, array2, array3, function(a, b, c) {
+        return a + b + c;
+      });
+
+      assert.deepEqual(actual, [12, 15, 18]);
+
+      var actual = _.zipWith(array1, [], function(a, b) {
+        return a + (b || 0);
+      });
+
+      assert.deepEqual(actual, [1, 2, 3]);
+    });
+
+    QUnit.test('should provide the correct `iteratee` arguments', function(assert) {
+      assert.expect(1);
+
+      var args;
+
+      _.zipWith([1, 2], [3, 4], [5, 6], function() {
+        args || (args = slice.call(arguments));
+      });
+
+      assert.deepEqual(args, [1, 3, 5]);
+    });
+
+    QUnit.test('should perform a basic zip when `iteratee` is nullish', function(assert) {
+      assert.expect(1);
+
+      var array1 = [1, 2],
+          array2 = [3, 4],
+          values = [, null, undefined],
+          expected = lodashStable.map(values, lodashStable.constant(_.zip(array1, array2)));
+
+      var actual = lodashStable.map(values, function(value, index) {
+        return index ? _.zipWith(array1, array2, value) : _.zipWith(array1, array2);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash.unzip and lodash.zip');
+
+  lodashStable.each(['unzip', 'zip'], function(methodName, index) {
+    var func = _[methodName];
+    func = lodashStable.bind(index ? func.apply : func.call, func, null);
+
+    var object = {
+      'an empty array': [
+        [],
+        []
+      ],
+      '0-tuples': [
+        [[], []],
+        []
+      ],
+      '2-tuples': [
+        [['barney', 'fred'], [36, 40]],
+        [['barney', 36], ['fred', 40]]
+      ],
+      '3-tuples': [
+        [['barney', 'fred'], [36, 40], [false, true]],
+        [['barney', 36, false], ['fred', 40, true]]
+      ]
+    };
+
+    lodashStable.forOwn(object, function(pair, key) {
+      QUnit.test('`_.' + methodName + '` should work with ' + key, function(assert) {
+        assert.expect(2);
+
+        var actual = func(pair[0]);
+        assert.deepEqual(actual, pair[1]);
+        assert.deepEqual(func(actual), actual.length ? pair[0] : []);
+      });
+    });
+
+    QUnit.test('`_.' + methodName + '` should work with tuples of different lengths', function(assert) {
+      assert.expect(4);
+
+      var pair = [
+        [['barney', 36], ['fred', 40, false]],
+        [['barney', 'fred'], [36, 40], [undefined, false]]
+      ];
+
+      var actual = func(pair[0]);
+      assert.ok('0' in actual[2]);
+      assert.deepEqual(actual, pair[1]);
+
+      actual = func(actual);
+      assert.ok('2' in actual[0]);
+      assert.deepEqual(actual, [['barney', 36, undefined], ['fred', 40, false]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should treat falsey values as empty arrays', function(assert) {
+      assert.expect(1);
+
+      var expected = lodashStable.map(falsey, alwaysEmptyArray);
+
+      var actual = lodashStable.map(falsey, function(value) {
+        return func([value, value, value]);
+      });
+
+      assert.deepEqual(actual, expected);
+    });
+
+    QUnit.test('`_.' + methodName + '` should ignore values that are not arrays or `arguments` objects', function(assert) {
+      assert.expect(1);
+
+      var array = [[1, 2], [3, 4], null, undefined, { '0': 1 }];
+      assert.deepEqual(func(array), [[1, 3], [2, 4]]);
+    });
+
+    QUnit.test('`_.' + methodName + '` should support consuming its return value', function(assert) {
+      assert.expect(1);
+
+      var expected = [['barney', 'fred'], [36, 40]];
+      assert.deepEqual(func(func(func(func(expected)))), expected);
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).commit');
+
+  (function() {
+    QUnit.test('should execute the chained sequence and returns the wrapped result', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var array = [1],
+            wrapped = _(array).push(2).push(3);
+
+        assert.deepEqual(array, [1]);
+
+        var otherWrapper = wrapped.commit();
+        assert.ok(otherWrapper instanceof _);
+        assert.deepEqual(otherWrapper.value(), [1, 2, 3]);
+        assert.deepEqual(wrapped.value(), [1, 2, 3, 2, 3]);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should track the `__chain__` value of a wrapper', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var wrapped = _([1]).chain().commit().head();
+        assert.ok(wrapped instanceof _);
+        assert.strictEqual(wrapped.value(), 1);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).next');
+
+  lodashStable.each([false, true], function(implict) {
+    function chain(value) {
+      return implict ? _(value) : _.chain(value);
+    }
+
+    var chainType = 'in an ' + (implict ? 'implict' : 'explict') + ' chain';
+
+    QUnit.test('should follow the iterator protocol ' + chainType, function(assert) {
+      assert.expect(3);
+
+      if (!isNpm) {
+        var wrapped = chain([1, 2]);
+
+        assert.deepEqual(wrapped.next(), { 'done': false, 'value': 1 });
+        assert.deepEqual(wrapped.next(), { 'done': false, 'value': 2 });
+        assert.deepEqual(wrapped.next(), { 'done': true,  'value': undefined });
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should act as an iterable ' + chainType, function(assert) {
+      assert.expect(2);
+
+      if (!isNpm && Symbol && Symbol.iterator) {
+        var array = [1, 2],
+            wrapped = chain(array);
+
+        assert.strictEqual(wrapped[Symbol.iterator](), wrapped);
+        assert.deepEqual(_.toArray(wrapped), array);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should use `_.toArray` to generate the iterable result ' + chainType, function(assert) {
+      assert.expect(3);
+
+      if (!isNpm && Array.from) {
+        var hearts = '\ud83d\udc95',
+            values = [[1], { 'a': 1 }, hearts];
+
+        lodashStable.each(values, function(value) {
+          var wrapped = chain(value);
+          assert.deepEqual(Array.from(wrapped), _.toArray(value));
+        });
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+
+    QUnit.test('should reset the iterator correctly ' + chainType, function(assert) {
+      assert.expect(4);
+
+      if (!isNpm && Symbol && Symbol.iterator) {
+        var array = [1, 2],
+            wrapped = chain(array);
+
+        assert.deepEqual(_.toArray(wrapped), array);
+        assert.deepEqual(_.toArray(wrapped), [], 'produces an empty array for exhausted iterator');
+
+        var other = wrapped.filter();
+        assert.deepEqual(_.toArray(other), array, 'reset for new chain segments');
+        assert.deepEqual(_.toArray(wrapped), [], 'iterator is still exhausted');
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should work in a lazy sequence ' + chainType, function(assert) {
+      assert.expect(3);
+
+      if (!isNpm && Symbol && Symbol.iterator) {
+        var array = lodashStable.range(LARGE_ARRAY_SIZE),
+            predicate = function(value) { values.push(value); return isEven(value); },
+            values = [],
+            wrapped = chain(array);
+
+        assert.deepEqual(_.toArray(wrapped), array);
+
+        wrapped = wrapped.filter(predicate);
+        assert.deepEqual(_.toArray(wrapped), _.filter(array, isEven), 'reset for new lazy chain segments');
+        assert.deepEqual(values, array, 'memoizes iterator values');
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).plant');
+
+  (function() {
+    QUnit.test('should clone the chained sequence planting `value` as the wrapped value', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array1 = [5, null, 3, null, 1],
+            array2 = [10, null, 8, null, 6],
+            wrapped1 = _(array1).thru(_.compact).map(square).takeRight(2).sort(),
+            wrapped2 = wrapped1.plant(array2);
+
+        assert.deepEqual(wrapped2.value(), [36, 64]);
+        assert.deepEqual(wrapped1.value(), [1, 9]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should clone `chainAll` settings', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var array1 = [2, 4],
+            array2 = [6, 8],
+            wrapped1 = _(array1).chain().map(square),
+            wrapped2 = wrapped1.plant(array2);
+
+        assert.deepEqual(wrapped2.head().value(), 36);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should reset iterator data on cloned sequences', function(assert) {
+      assert.expect(3);
+
+      if (!isNpm && Symbol && Symbol.iterator) {
+        var array1 = [2, 4],
+            array2 = [6, 8],
+            wrapped1 = _(array1).map(square);
+
+        assert.deepEqual(_.toArray(wrapped1), [4, 16]);
+        assert.deepEqual(_.toArray(wrapped1), []);
+
+        var wrapped2 = wrapped1.plant(array2);
+        assert.deepEqual(_.toArray(wrapped2), [36, 64]);
+      }
+      else {
+        skipAssert(assert, 3);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).pop');
+
+  (function() {
+    QUnit.test('should remove elements from the end of `array`', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var array = [1, 2],
+            wrapped = _(array);
+
+        assert.strictEqual(wrapped.pop(), 2);
+        assert.deepEqual(wrapped.value(), [1]);
+        assert.strictEqual(wrapped.pop(), 1);
+
+        var actual = wrapped.value();
+        assert.deepEqual(actual, []);
+        assert.strictEqual(actual, array);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            var result = index ? _(value).pop() : _().pop();
+            return result === undefined;
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).push');
+
+  (function() {
+    QUnit.test('should append elements to `array`', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array = [1],
+            wrapped = _(array).push(2, 3),
+            actual = wrapped.value();
+
+        assert.strictEqual(actual, array);
+        assert.deepEqual(actual, [1, 2, 3]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            var result = index ? _(value).push(1).value() : _().push(1).value();
+            return lodashStable.eq(result, value);
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).shift');
+
+  (function() {
+    QUnit.test('should remove elements from the front of `array`', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var array = [1, 2],
+            wrapped = _(array);
+
+        assert.strictEqual(wrapped.shift(), 1);
+        assert.deepEqual(wrapped.value(), [2]);
+        assert.strictEqual(wrapped.shift(), 2);
+
+        var actual = wrapped.value();
+        assert.deepEqual(actual, []);
+        assert.strictEqual(actual, array);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            var result = index ? _(value).shift() : _().shift();
+            return result === undefined;
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).sort');
+
+  (function() {
+    QUnit.test('should return the wrapped sorted `array`', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array = [3, 1, 2],
+            wrapped = _(array).sort(),
+            actual = wrapped.value();
+
+        assert.strictEqual(actual, array);
+        assert.deepEqual(actual, [1, 2, 3]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            var result = index ? _(value).sort().value() : _().sort().value();
+            return lodashStable.eq(result, value);
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).splice');
+
+  (function() {
+    QUnit.test('should support removing and inserting elements', function(assert) {
+      assert.expect(5);
+
+      if (!isNpm) {
+        var array = [1, 2],
+            wrapped = _(array);
+
+        assert.deepEqual(wrapped.splice(1, 1, 3).value(), [2]);
+        assert.deepEqual(wrapped.value(), [1, 3]);
+        assert.deepEqual(wrapped.splice(0, 2).value(), [1, 3]);
+
+        var actual = wrapped.value();
+        assert.deepEqual(actual, []);
+        assert.strictEqual(actual, array);
+      }
+      else {
+        skipAssert(assert, 5);
+      }
+    });
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            var result = index ? _(value).splice(0, 1).value() : _().splice(0, 1).value();
+            return lodashStable.isEqual(result, []);
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).unshift');
+
+  (function() {
+    QUnit.test('should prepend elements to `array`', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var array = [3],
+            wrapped = _(array).unshift(1, 2),
+            actual = wrapped.value();
+
+        assert.strictEqual(actual, array);
+        assert.deepEqual(actual, [1, 2, 3]);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var expected = lodashStable.map(falsey, alwaysTrue);
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            var result = index ? _(value).unshift(1).value() : _().unshift(1).value();
+            return lodashStable.eq(result, value);
+          } catch (e) {}
+        });
+
+        assert.deepEqual(actual, expected);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...).value');
+
+  (function() {
+    QUnit.test('should execute the chained sequence and extract the unwrapped value', function(assert) {
+      assert.expect(4);
+
+      if (!isNpm) {
+        var array = [1],
+            wrapped = _(array).push(2).push(3);
+
+        assert.deepEqual(array, [1]);
+        assert.deepEqual(wrapped.value(), [1, 2, 3]);
+        assert.deepEqual(wrapped.value(), [1, 2, 3, 2, 3]);
+        assert.deepEqual(array, [1, 2, 3, 2, 3]);
+      }
+      else {
+        skipAssert(assert, 4);
+      }
+    });
+
+    QUnit.test('should return the `valueOf` result of the wrapped value', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm) {
+        var wrapped = _(123);
+        assert.strictEqual(Number(wrapped), 123);
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should stringify the wrapped value when used by `JSON.stringify`', function(assert) {
+      assert.expect(1);
+
+      if (!isNpm && JSON) {
+        var wrapped = _([1, 2, 3]);
+        assert.strictEqual(JSON.stringify(wrapped), '[1,2,3]');
+      }
+      else {
+        skipAssert(assert);
+      }
+    });
+
+    QUnit.test('should be aliased', function(assert) {
+      assert.expect(2);
+
+      if (!isNpm) {
+        var expected = _.prototype.value;
+        assert.strictEqual(_.prototype.toJSON, expected);
+        assert.strictEqual(_.prototype.valueOf, expected);
+      }
+      else {
+        skipAssert(assert, 2);
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...) methods that return the wrapped modified array');
+
+  (function() {
+    var funcs = [
+      'push',
+      'reverse',
+      'sort',
+      'unshift'
+    ];
+
+    lodashStable.each(funcs, function(methodName) {
+      QUnit.test('`_(...).' + methodName + '` should return a new wrapper', function(assert) {
+        assert.expect(2);
+
+        if (!isNpm) {
+          var array = [1, 2, 3],
+              wrapped = _(array),
+              actual = wrapped[methodName]();
+
+          assert.ok(actual instanceof _);
+          assert.notStrictEqual(actual, wrapped);
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...) methods that return new wrapped values');
+
+  (function() {
+    var funcs = [
+      'castArray',
+      'concat',
+      'difference',
+      'differenceBy',
+      'differenceWith',
+      'intersection',
+      'intersectionBy',
+      'intersectionWith',
+      'pull',
+      'pullAll',
+      'pullAt',
+      'sampleSize',
+      'shuffle',
+      'slice',
+      'splice',
+      'split',
+      'toArray',
+      'union',
+      'unionBy',
+      'unionWith',
+      'uniq',
+      'uniqBy',
+      'uniqWith',
+      'words',
+      'xor',
+      'xorBy',
+      'xorWith'
+    ];
+
+    lodashStable.each(funcs, function(methodName) {
+      QUnit.test('`_(...).' + methodName + '` should return a new wrapped value', function(assert) {
+        assert.expect(2);
+
+        if (!isNpm) {
+          var value = methodName == 'split' ? 'abc' : [1, 2, 3],
+              wrapped = _(value),
+              actual = wrapped[methodName]();
+
+          assert.ok(actual instanceof _);
+          assert.notStrictEqual(actual, wrapped);
+        }
+        else {
+          skipAssert(assert, 2);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash(...) methods that return unwrapped values');
+
+  (function() {
+    var funcs = [
+      'add',
+      'camelCase',
+      'capitalize',
+      'ceil',
+      'clone',
+      'deburr',
+      'divide',
+      'endsWith',
+      'escape',
+      'escapeRegExp',
+      'every',
+      'find',
+      'floor',
+      'has',
+      'hasIn',
+      'head',
+      'includes',
+      'isArguments',
+      'isArray',
+      'isArrayBuffer',
+      'isArrayLike',
+      'isBoolean',
+      'isBuffer',
+      'isDate',
+      'isElement',
+      'isEmpty',
+      'isEqual',
+      'isError',
+      'isFinite',
+      'isFunction',
+      'isInteger',
+      'isMap',
+      'isNaN',
+      'isNative',
+      'isNil',
+      'isNull',
+      'isNumber',
+      'isObject',
+      'isObjectLike',
+      'isPlainObject',
+      'isRegExp',
+      'isSafeInteger',
+      'isSet',
+      'isString',
+      'isUndefined',
+      'isWeakMap',
+      'isWeakSet',
+      'join',
+      'kebabCase',
+      'last',
+      'lowerCase',
+      'lowerFirst',
+      'max',
+      'maxBy',
+      'min',
+      'minBy',
+      'multiply',
+      'nth',
+      'pad',
+      'padEnd',
+      'padStart',
+      'parseInt',
+      'pop',
+      'random',
+      'reduce',
+      'reduceRight',
+      'repeat',
+      'replace',
+      'round',
+      'sample',
+      'shift',
+      'size',
+      'snakeCase',
+      'some',
+      'startCase',
+      'startsWith',
+      'subtract',
+      'sum',
+      'toInteger',
+      'toLower',
+      'toNumber',
+      'toSafeInteger',
+      'toString',
+      'toUpper',
+      'trim',
+      'trimEnd',
+      'trimStart',
+      'truncate',
+      'unescape',
+      'upperCase',
+      'upperFirst'
+    ];
+
+    lodashStable.each(funcs, function(methodName) {
+      QUnit.test('`_(...).' + methodName + '` should return an unwrapped value when implicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          var actual = _()[methodName]();
+          assert.notOk(actual instanceof _);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+
+      QUnit.test('`_(...).' + methodName + '` should return a wrapped value when explicitly chaining', function(assert) {
+        assert.expect(1);
+
+        if (!isNpm) {
+          var actual = _().chain()[methodName]();
+          assert.ok(actual instanceof _);
+        }
+        else {
+          skipAssert(assert);
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('"Arrays" category methods');
+
+  (function() {
+    var args = (function() { return arguments; }(1, null, [3], null, 5)),
+        sortedArgs = (function() { return arguments; }(1, [3], 5, null, null)),
+        array = [1, 2, 3, 4, 5, 6];
+
+    QUnit.test('should work with `arguments` objects', function(assert) {
+      assert.expect(30);
+
+      function message(methodName) {
+        return '`_.' + methodName + '` should work with `arguments` objects';
+      }
+
+      assert.deepEqual(_.difference(args, [null]), [1, [3], 5], message('difference'));
+      assert.deepEqual(_.difference(array, args), [2, 3, 4, 6], '_.difference should work with `arguments` objects as secondary arguments');
+
+      assert.deepEqual(_.union(args, [null, 6]), [1, null, [3], 5, 6], message('union'));
+      assert.deepEqual(_.union(array, args), array.concat([null, [3]]), '_.union should work with `arguments` objects as secondary arguments');
+
+      assert.deepEqual(_.compact(args), [1, [3], 5], message('compact'));
+      assert.deepEqual(_.drop(args, 3), [null, 5], message('drop'));
+      assert.deepEqual(_.dropRight(args, 3), [1, null], message('dropRight'));
+      assert.deepEqual(_.dropRightWhile(args,identity), [1, null, [3], null], message('dropRightWhile'));
+      assert.deepEqual(_.dropWhile(args,identity), [null, [3], null, 5], message('dropWhile'));
+      assert.deepEqual(_.findIndex(args, identity), 0, message('findIndex'));
+      assert.deepEqual(_.findLastIndex(args, identity), 4, message('findLastIndex'));
+      assert.deepEqual(_.flatten(args), [1, null, 3, null, 5], message('flatten'));
+      assert.deepEqual(_.head(args), 1, message('head'));
+      assert.deepEqual(_.indexOf(args, 5), 4, message('indexOf'));
+      assert.deepEqual(_.initial(args), [1, null, [3], null], message('initial'));
+      assert.deepEqual(_.intersection(args, [1]), [1], message('intersection'));
+      assert.deepEqual(_.last(args), 5, message('last'));
+      assert.deepEqual(_.lastIndexOf(args, 1), 0, message('lastIndexOf'));
+      assert.deepEqual(_.sortedIndex(sortedArgs, 6), 3, message('sortedIndex'));
+      assert.deepEqual(_.sortedIndexOf(sortedArgs, 5), 2, message('sortedIndexOf'));
+      assert.deepEqual(_.sortedLastIndex(sortedArgs, 5), 3, message('sortedLastIndex'));
+      assert.deepEqual(_.sortedLastIndexOf(sortedArgs, 1), 0, message('sortedLastIndexOf'));
+      assert.deepEqual(_.tail(args, 4), [null, [3], null, 5], message('tail'));
+      assert.deepEqual(_.take(args, 2), [1, null], message('take'));
+      assert.deepEqual(_.takeRight(args, 1), [5], message('takeRight'));
+      assert.deepEqual(_.takeRightWhile(args, identity), [5], message('takeRightWhile'));
+      assert.deepEqual(_.takeWhile(args, identity), [1], message('takeWhile'));
+      assert.deepEqual(_.uniq(args), [1, null, [3], 5], message('uniq'));
+      assert.deepEqual(_.without(args, null), [1, [3], 5], message('without'));
+      assert.deepEqual(_.zip(args, args), [[1, 1], [null, null], [[3], [3]], [null, null], [5, 5]], message('zip'));
+    });
+
+    QUnit.test('should accept falsey primary arguments', function(assert) {
+      assert.expect(4);
+
+      function message(methodName) {
+        return '`_.' + methodName + '` should accept falsey primary arguments';
+      }
+
+      assert.deepEqual(_.difference(null, array), [], message('difference'));
+      assert.deepEqual(_.intersection(null, array), [], message('intersection'));
+      assert.deepEqual(_.union(null, array), array, message('union'));
+      assert.deepEqual(_.xor(null, array), array, message('xor'));
+    });
+
+    QUnit.test('should accept falsey secondary arguments', function(assert) {
+      assert.expect(3);
+
+      function message(methodName) {
+        return '`_.' + methodName + '` should accept falsey secondary arguments';
+      }
+
+      assert.deepEqual(_.difference(array, null), array, message('difference'));
+      assert.deepEqual(_.intersection(array, null), [], message('intersection'));
+      assert.deepEqual(_.union(array, null), array, message('union'));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('"Strings" category methods');
+
+  (function() {
+    var stringMethods = [
+      'camelCase',
+      'capitalize',
+      'escape',
+      'kebabCase',
+      'lowerCase',
+      'lowerFirst',
+      'pad',
+      'padEnd',
+      'padStart',
+      'repeat',
+      'snakeCase',
+      'toLower',
+      'toUpper',
+      'trim',
+      'trimEnd',
+      'trimStart',
+      'truncate',
+      'unescape',
+      'upperCase',
+      'upperFirst'
+    ];
+
+    lodashStable.each(stringMethods, function(methodName) {
+      var func = _[methodName];
+
+      QUnit.test('`_.' + methodName + '` should return an empty string for empty values', function(assert) {
+        assert.expect(1);
+
+        var values = [, null, undefined, ''],
+            expected = lodashStable.map(values, alwaysEmptyString);
+
+        var actual = lodashStable.map(values, function(value, index) {
+          return index ? func(value) : func();
+        });
+
+        assert.deepEqual(actual, expected);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('lodash methods');
+
+  (function() {
+    var allMethods = lodashStable.reject(_.functions(_).sort(), function(methodName) {
+      return lodashStable.startsWith(methodName, '_');
+    });
+
+    var checkFuncs = [
+      'after',
+      'ary',
+      'before',
+      'bind',
+      'curry',
+      'curryRight',
+      'debounce',
+      'defer',
+      'delay',
+      'flip',
+      'flow',
+      'flowRight',
+      'memoize',
+      'negate',
+      'once',
+      'partial',
+      'partialRight',
+      'rearg',
+      'rest',
+      'spread',
+      'throttle',
+      'unary'
+    ];
+
+    var noBinding = [
+      'flip',
+      'memoize',
+      'negate',
+      'once',
+      'overArgs',
+      'partial',
+      'partialRight',
+      'rearg',
+      'rest',
+      'spread'
+    ];
+
+    var rejectFalsey = [
+      'tap',
+      'thru'
+    ].concat(checkFuncs);
+
+    var returnArrays = [
+      'at',
+      'chunk',
+      'compact',
+      'difference',
+      'drop',
+      'filter',
+      'flatten',
+      'functions',
+      'initial',
+      'intersection',
+      'invokeMap',
+      'keys',
+      'map',
+      'orderBy',
+      'pull',
+      'pullAll',
+      'pullAt',
+      'range',
+      'rangeRight',
+      'reject',
+      'remove',
+      'shuffle',
+      'sortBy',
+      'tail',
+      'take',
+      'times',
+      'toArray',
+      'toPairs',
+      'toPairsIn',
+      'union',
+      'uniq',
+      'values',
+      'without',
+      'xor',
+      'zip'
+    ];
+
+    var acceptFalsey = lodashStable.difference(allMethods, rejectFalsey);
+
+    QUnit.test('should accept falsey arguments', function(assert) {
+      assert.expect(308);
+
+      var emptyArrays = lodashStable.map(falsey, alwaysEmptyArray);
+
+      lodashStable.each(acceptFalsey, function(methodName) {
+        var expected = emptyArrays,
+            func = _[methodName],
+            pass = true;
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          try {
+            return index ? func(value) : func();
+          } catch (e) {
+            pass = false;
+          }
+        });
+
+        if (methodName == 'noConflict') {
+          root._ = oldDash;
+        }
+        else if (methodName == 'pull' || methodName == 'pullAll') {
+          expected = falsey;
+        }
+        if (lodashStable.includes(returnArrays, methodName) && methodName != 'sample') {
+          assert.deepEqual(actual, expected, '_.' + methodName + ' returns an array');
+        }
+        assert.ok(pass, '`_.' + methodName + '` accepts falsey arguments');
+      });
+
+      // Skip tests for missing methods of modularized builds.
+      lodashStable.each(['chain', 'noConflict', 'runInContext'], function(methodName) {
+        if (!_[methodName]) {
+          skipAssert(assert);
+        }
+      });
+    });
+
+    QUnit.test('should return an array', function(assert) {
+      assert.expect(70);
+
+      var array = [1, 2, 3];
+
+      lodashStable.each(returnArrays, function(methodName) {
+        var actual,
+            func = _[methodName];
+
+        switch (methodName) {
+          case 'invokeMap':
+            actual = func(array, 'toFixed');
+            break;
+          case 'sample':
+            actual = func(array, 1);
+            break;
+          default:
+            actual = func(array);
+        }
+        assert.ok(lodashStable.isArray(actual), '_.' + methodName + ' returns an array');
+
+        var isPull = methodName == 'pull' || methodName == 'pullAll';
+        assert.strictEqual(actual === array, isPull, '_.' + methodName + ' should ' + (isPull ? '' : 'not ') + 'return the given array');
+      });
+    });
+
+    QUnit.test('should throw an error for falsey arguments', function(assert) {
+      assert.expect(24);
+
+      lodashStable.each(rejectFalsey, function(methodName) {
+        var expected = lodashStable.map(falsey, alwaysTrue),
+            func = _[methodName];
+
+        var actual = lodashStable.map(falsey, function(value, index) {
+          var pass = !index && /^(?:backflow|compose|cond|flow(Right)?|over(?:Every|Some)?)$/.test(methodName);
+
+          try {
+            index ? func(value) : func();
+          } catch (e) {
+            pass = !pass && (e instanceof TypeError) &&
+              (!lodashStable.includes(checkFuncs, methodName) || (e.message == FUNC_ERROR_TEXT));
+          }
+          return pass;
+        });
+
+        assert.deepEqual(actual, expected, '`_.' + methodName + '` rejects falsey arguments');
+      });
+    });
+
+    QUnit.test('should not set a `this` binding', function(assert) {
+      assert.expect(30);
+
+      lodashStable.each(noBinding, function(methodName) {
+        var fn = function() { return this.a; },
+            func = _[methodName],
+            isNegate = methodName == 'negate',
+            object = { 'a': 1 },
+            expected = isNegate ? false : 1;
+
+        var wrapper = func(_.bind(fn, object));
+        assert.strictEqual(wrapper(), expected, '`_.' + methodName + '` can consume a bound function');
+
+        wrapper = _.bind(func(fn), object);
+        assert.strictEqual(wrapper(), expected, '`_.' + methodName + '` can be bound');
+
+        object.wrapper = func(fn);
+        assert.strictEqual(object.wrapper(), expected, '`_.' + methodName + '` uses the `this` of its parent object');
+      });
+    });
+
+    QUnit.test('should not contain minified method names (test production builds)', function(assert) {
+      assert.expect(1);
+
+      var shortNames = ['_', 'at', 'eq', 'gt', 'lt'];
+      assert.ok(lodashStable.every(_.functions(_), function(methodName) {
+        return methodName.length > 2 || lodashStable.includes(shortNames, methodName);
+      }));
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.config.asyncRetries = 10;
+  QUnit.config.hidepassed = true;
+
+  if (!document) {
+    QUnit.config.noglobals = true;
+    QUnit.load();
+  }
+}.call(this));
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/test/underscore.html b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/underscore.html
new file mode 100644
index 0000000..a3e56e8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/test/underscore.html
@@ -0,0 +1,484 @@
+<!doctype html>
+<html lang="en">
+  <head>
+    <meta charset="utf-8">
+    <title>Underscore Test Suite</title>
+    <link rel="stylesheet" href="../node_modules/qunitjs/qunit/qunit.css">
+  </head>
+  <body>
+    <div id="qunit"></div>
+    <script>
+      // Avoid reporting tests to Sauce Labs when script errors occur.
+      if (location.port == '9001') {
+        window.onerror = function(message) {
+          if (window.QUnit) {
+            QUnit.config.done.length = 0;
+          }
+          global_test_results = { 'message': message };
+        };
+      }
+    </script>
+    <script src="../node_modules/qunitjs/qunit/qunit.js"></script>
+    <script src="../node_modules/qunit-extras/qunit-extras.js"></script>
+    <script src="../node_modules/jquery/dist/jquery.js"></script>
+    <script src="../node_modules/platform/platform.js"></script>
+    <script src="./asset/test-ui.js"></script>
+    <script src="../lodash.js"></script>
+    <script>
+      QUnit.config.asyncRetries = 10;
+      QUnit.config.hidepassed = true;
+      QUnit.config.excused = {
+        'Arrays': {
+          'chunk': [
+            'defaults to empty array (chunk size 0)'
+          ],
+          'difference': [
+            'can perform an OO-style difference'
+          ],
+          'drop': [
+            'is an alias for rest'
+          ],
+          'first': [
+            'returns an empty array when n <= 0 (0 case)',
+            'returns an empty array when n <= 0 (negative case)',
+            'can fetch the first n elements',
+            'returns the whole array if n > length'
+          ],
+          'findIndex': [
+            'called with context'
+          ],
+          'findLastIndex': [
+            'called with context'
+          ],
+          'flatten': [
+            'supports empty arrays',
+            'can flatten nested arrays',
+            'works on an arguments object',
+            'can handle very deep arrays'
+          ],
+          'head': [
+            'is an alias for first'
+          ],
+          'indexOf': [
+            "sorted indexOf doesn't uses binary search",
+            '0'
+          ],
+          'initial': [
+            'returns all but the last n elements',
+            'returns an empty array when n > length',
+            'works on an arguments object'
+          ],
+          'intersection': [
+            'can perform an OO-style intersection'
+          ],
+          'last': [
+            'returns an empty array when n <= 0 (0 case)',
+            'returns an empty array when n <= 0 (negative case)',
+            'can fetch the last n elements',
+            'returns the whole array if n > length'
+          ],
+          'lastIndexOf': [
+            'should treat falsey `fromIndex` values, except `0` and `NaN`, as `array.length`',
+            'should treat non-number `fromIndex` values as `array.length`',
+            '[0,-1,-1]'
+          ],
+          'object': [
+            'an array of pairs zipped together into an object',
+            'an object converted to pairs and back to an object'
+          ],
+          'range': [
+            'range with two arguments a &amp; b, b&lt;a generates an empty array'
+          ],
+          'rest': [
+            'returns the whole array when index is 0',
+            'returns elements starting at the given index',
+            'works on an arguments object'
+          ],
+          'sortedIndex': [
+            '2',
+            '3'
+          ],
+          'tail': [
+            'is an alias for rest'
+          ],
+          'take': [
+            'is an alias for first'
+          ],
+          'uniq': [
+            'uses the result of `iterator` for uniqueness comparisons (unsorted case)',
+            '`sorted` argument defaults to false when omitted',
+            'when `iterator` is a string, uses that key for comparisons (unsorted case)',
+            'uses the result of `iterator` for uniqueness comparisons (sorted case)',
+            'when `iterator` is a string, uses that key for comparisons (sorted case)',
+            'can use falsey pluck like iterator'
+          ],
+          'union': [
+            'can perform an OO-style union'
+          ]
+        },
+        'Chaining': {
+          'pop': true,
+          'shift': true,
+          'splice': true,
+          'reverse/concat/unshift/pop/map': [
+            'can chain together array functions.'
+          ]
+        },
+        'Collections': {
+          'lookupIterator with contexts': true,
+          'Iterating objects with sketchy length properties': true,
+          'Resistant to collection length and properties changing while iterating': true,
+          'countBy': [
+            'true'
+          ],
+          'each': [
+            'context object property accessed'
+          ],
+          'every': [
+            'Can be called with object',
+            'Died on test #15',
+            'context works'
+          ],
+          'filter': [
+            'given context',
+            '[{"a":1,"b":2},{"a":1,"b":3},{"a":1,"b":4}]',
+            '[{"a":1,"b":2},{"a":2,"b":2}]',
+            'Empty object accepts all items',
+            'OO-filter'
+          ],
+          'find': [
+            '{"a":1,"b":4}',
+            'undefined when not found',
+            'undefined when searching empty list',
+            'works on objects',
+            'undefined',
+            'called with context'
+          ],
+          'findWhere': [
+            'checks properties given function'
+          ],
+          'groupBy': [
+            'true'
+          ],
+          'includes': [
+            "doesn't delegate to binary search"
+          ],
+          'invoke': [
+            'handles null & undefined'
+          ],
+          'map': [
+            'tripled numbers with context',
+            'OO-style doubled numbers'
+          ],
+          'max': [
+            'can handle null/undefined',
+            'can perform a computation-based max',
+            'Maximum value of an empty object',
+            'Maximum value of an empty array',
+            'Maximum value of a non-numeric collection',
+            'Finds correct max in array starting with num and containing a NaN',
+            'Finds correct max in array starting with NaN',
+            'Respects iterator return value of -Infinity',
+            'String keys use property iterator',
+            'Iterator context',
+            'Lookup falsy iterator'
+          ],
+          'min': [
+            'can handle null/undefined',
+            'can perform a computation-based min',
+            'Minimum value of an empty object',
+            'Minimum value of an empty array',
+            'Minimum value of a non-numeric collection',
+            'Finds correct min in array starting with NaN',
+            'Respects iterator return value of Infinity',
+            'String keys use property iterator',
+            'Iterator context',
+            'Lookup falsy iterator'
+          ],
+          'partition': [
+            'can reference the array index',
+            'Died on test #8',
+            'partition takes a context argument',
+            'function(a){[code]}'
+          ],
+          'pluck': [
+            '[1]'
+          ],
+          'reduce': [
+            'can reduce with a context object'
+          ],
+          'reject': [
+            'Returns empty list given empty array'
+          ],
+          'sample': [
+            'behaves correctly on negative n',
+            'Died on test #3'
+          ],
+          'some': [
+            'Can be called with object',
+            'Died on test #17',
+            'context works'
+          ],
+          'where': [
+            'checks properties given function'
+          ],
+          'Can use various collection methods on NodeLists': [
+            '<span id="id2"></span>',
+            '<span id="id1"></span>'
+          ]
+        },
+        'Functions': {
+          'debounce asap': true,
+          'debounce asap cancel': true,
+          'debounce after system time is set backwards': true,
+          'debounce asap recursively': true,
+          'throttle repeatedly with results': true,
+          'more throttle does not trigger leading call when leading is set to false': true,
+          'throttle does not trigger trailing call when trailing is set to false': true,
+          'before': [
+            'stores a memo to the last value',
+            'provides context'
+          ],
+          'bind': [
+            'Died on test #2'
+          ],
+          'bindAll': [
+            'throws an error for bindAll with no functions named'
+          ],
+          'memoize': [
+            '{"bar":"BAR","foo":"FOO"}',
+            'Died on test #8'
+          ],
+          'partial':[
+            'can partially apply with placeholders',
+            'accepts more arguments than the number of placeholders',
+            'accepts fewer arguments than the number of placeholders',
+            'unfilled placeholders are undefined',
+            'keeps prototype',
+            'allows the placeholder to be swapped out'
+          ]
+        },
+        'Objects': {
+          '#1929 Typed Array constructors are functions': true,
+          'allKeys': [
+            'is not fooled by sparse arrays; see issue #95',
+            'is not fooled by sparse arrays with additional properties',
+            '[]'
+          ],
+          'defaults': [
+            'defaults skips nulls',
+            'defaults skips undefined'
+          ],
+          'extend': [
+            'extending null results in null',
+            'extending undefined results in undefined'
+          ],
+          'extendOwn': [
+            'extending non-objects results in returning the non-object value',
+            'extending undefined results in undefined'
+          ],
+          'functions': [
+            'also looks up functions on the prototype'
+          ],
+          'isEqual': [
+            '`0` is not equal to `-0`',
+            'Commutative equality is implemented for `0` and `-0`',
+            '`new Number(0)` and `-0` are not equal',
+            'Commutative equality is implemented for `new Number(0)` and `-0`',
+            'false'
+          ],
+          'isFinite': [
+            'Numeric strings are numbers',
+            'Number instances can be finite'
+          ],
+          'isMatch': [
+            'doesnt falsey match constructor on undefined/null'
+          ],
+          'isSet': [
+            'Died on test #9'
+          ],
+          'findKey': [
+            'called with context'
+          ],
+          'keys': [
+            'is not fooled by sparse arrays; see issue #95',
+            '[]'
+          ],
+          'mapObject': [
+            'keep context',
+            'called with context',
+            'mapValue identity'
+          ],
+          'matcher': [
+            'null matches null',
+            'treats primitives as empty'
+          ],
+          'omit': [
+            'can accept a predicate',
+            'function is given context'
+          ],
+          'pick': [
+            'can accept a predicate and context',
+            'function is given context'
+          ]
+        },
+        'Utility': {
+          'noConflict (node vm)': true,
+          'now': [
+            'Produces the correct time in milliseconds'
+          ],
+          'times': [
+            'works as a wrapper'
+          ]
+        }
+      };
+
+      var mixinPrereqs = (function() {
+        var aliasToReal = {
+          'all': 'every',
+          'allKeys': 'keysIn',
+          'any': 'some',
+          'collect': 'map',
+          'compose': 'flowRight',
+          'contains': 'includes',
+          'detect': 'find',
+          'extendOwn': 'assign',
+          'findWhere': 'find',
+          'foldl': 'reduce',
+          'foldr': 'reduceRight',
+          'include': 'includes',
+          'indexBy': 'keyBy',
+          'inject': 'reduce',
+          'invoke': 'invokeMap',
+          'mapObject': 'mapValues',
+          'matcher': 'matches',
+          'methods': 'functions',
+          'object': 'zipObject',
+          'pairs': 'toPairs',
+          'pluck': 'map',
+          'restParam': 'restArgs',
+          'select': 'filter',
+          'unique': 'uniq',
+          'where': 'filter'
+        };
+
+        var keyMap = {
+          'rest': 'tail',
+          'restArgs': 'rest'
+        };
+
+        var lodash = _.noConflict();
+
+        return function(_) {
+          lodash.defaultsDeep(_, { 'templateSettings': lodash.templateSettings });
+          lodash.mixin(_, lodash.pick(lodash, lodash.difference(lodash.functions(lodash), lodash.functions(_))));
+
+          lodash.forOwn(keyMap, function(realName, otherName) {
+            _[otherName] = lodash[realName];
+            _.prototype[otherName] = lodash.prototype[realName];
+          });
+
+          lodash.forOwn(aliasToReal, function(realName, alias) {
+            _[alias] = _[realName];
+            _.prototype[alias] = _.prototype[realName];
+          });
+        };
+      }());
+
+      // Only excuse in Sauce Labs.
+      if (!ui.isSauceLabs) {
+        delete QUnit.config.excused.Functions['throttle does not trigger trailing call when trailing is set to false'];
+        delete QUnit.config.excused.Utility.now;
+      }
+      // Load prerequisite scripts.
+      document.write(ui.urlParams.loader == 'none'
+        ? '<script src="' + ui.buildPath + '"><\/script>'
+        : '<script data-dojo-config="async:1" src="' + ui.loaderPath + '"><\/script>'
+      );
+    </script>
+    <script>
+      if (ui.urlParams.loader == 'none') {
+        mixinPrereqs(_);
+        document.write([
+          '<script src="../vendor/underscore/test/collections.js"><\/script>',
+          '<script src="../vendor/underscore/test/arrays.js"><\/script>',
+          '<script src="../vendor/underscore/test/functions.js"><\/script>',
+          '<script src="../vendor/underscore/test/objects.js"><\/script>',
+          '<script src="../vendor/underscore/test/cross-document.js"><\/script>',
+          '<script src="../vendor/underscore/test/utility.js"><\/script>',
+          '<script src="../vendor/underscore/test/chaining.js"><\/script>'
+        ].join('\n'));
+      }
+    </script>
+    <script>
+      (function() {
+        if (window.curl) {
+          curl.config({ 'apiName': 'require' });
+        }
+        if (!window.require) {
+          return;
+        }
+        // Wrap to work around tests assuming Node `require` use.
+        require = (function(func) {
+          return function() {
+            return arguments[0] === '..' ? window._ : func.apply(null, arguments);
+          };
+        }(require));
+
+        var reBasename = /[\w.-]+$/,
+            basePath = ('//' + location.host + location.pathname.replace(reBasename, '')).replace(/\btest\/$/, ''),
+            modulePath = ui.buildPath.replace(/\.js$/, ''),
+            locationPath = modulePath.replace(reBasename, '').replace(/^\/|\/$/g, ''),
+            moduleId = /\bunderscore\b/i.test(ui.buildPath) ? 'underscore' : 'lodash',
+            moduleMain = modulePath.match(reBasename)[0],
+            uid = +new Date;
+
+        function getConfig() {
+          var result = {
+            'baseUrl': './',
+            'urlArgs': 't=' + uid++,
+            'waitSeconds': 0,
+            'paths': {},
+            'packages': [{
+              'name': 'test',
+              'location': '../vendor/underscore/test',
+              'config': {
+                // Work around no global being exported.
+                'exports': 'QUnit',
+                'loader': 'curl/loader/legacy'
+              }
+            }]
+          };
+
+          if (ui.isModularize) {
+            result.packages.push({
+              'name': moduleId,
+              'location': locationPath,
+              'main': moduleMain
+            });
+          } else {
+            result.paths[moduleId] = modulePath;
+          }
+          return result;
+        }
+
+        QUnit.config.autostart = false;
+
+        require(getConfig(), [moduleId], function(lodash) {
+          mixinPrereqs(lodash);
+          require(getConfig(), [
+            'test/collections',
+            'test/arrays',
+            'test/functions',
+            'test/objects',
+            'test/cross-document',
+            'test/utility',
+            'test/chaining'
+          ], function() {
+            QUnit.start();
+          });
+        });
+      }());
+    </script>
+  </body>
+</html>
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/LICENSE b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/LICENSE
new file mode 100644
index 0000000..02c89b2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2010-2016 Jeremy Ashkenas, DocumentCloud
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/backbone.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/backbone.js
new file mode 100644
index 0000000..55ccb22
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/backbone.js
@@ -0,0 +1,1920 @@
+//     Backbone.js 1.3.3
+
+//     (c) 2010-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Backbone may be freely distributed under the MIT license.
+//     For all details and documentation:
+//     http://backbonejs.org
+
+(function(factory) {
+
+  // Establish the root object, `window` (`self`) in the browser, or `global` on the server.
+  // We use `self` instead of `window` for `WebWorker` support.
+  var root = (typeof self == 'object' && self.self === self && self) ||
+            (typeof global == 'object' && global.global === global && global);
+
+  // Set up Backbone appropriately for the environment. Start with AMD.
+  if (typeof define === 'function' && define.amd) {
+    define(['underscore', 'jquery', 'exports'], function(_, $, exports) {
+      // Export global even in AMD case in case this script is loaded with
+      // others that may still expect a global Backbone.
+      root.Backbone = factory(root, exports, _, $);
+    });
+
+  // Next for Node.js or CommonJS. jQuery may not be needed as a module.
+  } else if (typeof exports !== 'undefined') {
+    var _ = require('underscore'), $;
+    try { $ = require('jquery'); } catch (e) {}
+    factory(root, exports, _, $);
+
+  // Finally, as a browser global.
+  } else {
+    root.Backbone = factory(root, {}, root._, (root.jQuery || root.Zepto || root.ender || root.$));
+  }
+
+})(function(root, Backbone, _, $) {
+
+  // Initial Setup
+  // -------------
+
+  // Save the previous value of the `Backbone` variable, so that it can be
+  // restored later on, if `noConflict` is used.
+  var previousBackbone = root.Backbone;
+
+  // Create a local reference to a common array method we'll want to use later.
+  var slice = Array.prototype.slice;
+
+  // Current version of the library. Keep in sync with `package.json`.
+  Backbone.VERSION = '1.3.3';
+
+  // For Backbone's purposes, jQuery, Zepto, Ender, or My Library (kidding) owns
+  // the `$` variable.
+  Backbone.$ = $;
+
+  // Runs Backbone.js in *noConflict* mode, returning the `Backbone` variable
+  // to its previous owner. Returns a reference to this Backbone object.
+  Backbone.noConflict = function() {
+    root.Backbone = previousBackbone;
+    return this;
+  };
+
+  // Turn on `emulateHTTP` to support legacy HTTP servers. Setting this option
+  // will fake `"PATCH"`, `"PUT"` and `"DELETE"` requests via the `_method` parameter and
+  // set a `X-Http-Method-Override` header.
+  Backbone.emulateHTTP = false;
+
+  // Turn on `emulateJSON` to support legacy servers that can't deal with direct
+  // `application/json` requests ... this will encode the body as
+  // `application/x-www-form-urlencoded` instead and will send the model in a
+  // form param named `model`.
+  Backbone.emulateJSON = false;
+
+  // Proxy Backbone class methods to Underscore functions, wrapping the model's
+  // `attributes` object or collection's `models` array behind the scenes.
+  //
+  // collection.filter(function(model) { return model.get('age') > 10 });
+  // collection.each(this.addView);
+  //
+  // `Function#apply` can be slow so we use the method's arg count, if we know it.
+  var addMethod = function(length, method, attribute) {
+    switch (length) {
+      case 1: return function() {
+        return _[method](this[attribute]);
+      };
+      case 2: return function(value) {
+        return _[method](this[attribute], value);
+      };
+      case 3: return function(iteratee, context) {
+        return _[method](this[attribute], cb(iteratee, this), context);
+      };
+      case 4: return function(iteratee, defaultVal, context) {
+        return _[method](this[attribute], cb(iteratee, this), defaultVal, context);
+      };
+      default: return function() {
+        var args = slice.call(arguments);
+        args.unshift(this[attribute]);
+        return _[method].apply(_, args);
+      };
+    }
+  };
+  var addUnderscoreMethods = function(Class, methods, attribute) {
+    _.each(methods, function(length, method) {
+      if (_[method]) Class.prototype[method] = addMethod(length, method, attribute);
+    });
+  };
+
+  // Support `collection.sortBy('attr')` and `collection.findWhere({id: 1})`.
+  var cb = function(iteratee, instance) {
+    if (_.isFunction(iteratee)) return iteratee;
+    if (_.isObject(iteratee) && !instance._isModel(iteratee)) return modelMatcher(iteratee);
+    if (_.isString(iteratee)) return function(model) { return model.get(iteratee); };
+    return iteratee;
+  };
+  var modelMatcher = function(attrs) {
+    var matcher = _.matches(attrs);
+    return function(model) {
+      return matcher(model.attributes);
+    };
+  };
+
+  // Backbone.Events
+  // ---------------
+
+  // A module that can be mixed in to *any object* in order to provide it with
+  // a custom event channel. You may bind a callback to an event with `on` or
+  // remove with `off`; `trigger`-ing an event fires all callbacks in
+  // succession.
+  //
+  //     var object = {};
+  //     _.extend(object, Backbone.Events);
+  //     object.on('expand', function(){ alert('expanded'); });
+  //     object.trigger('expand');
+  //
+  var Events = Backbone.Events = {};
+
+  // Regular expression used to split event strings.
+  var eventSplitter = /\s+/;
+
+  // Iterates over the standard `event, callback` (as well as the fancy multiple
+  // space-separated events `"change blur", callback` and jQuery-style event
+  // maps `{event: callback}`).
+  var eventsApi = function(iteratee, events, name, callback, opts) {
+    var i = 0, names;
+    if (name && typeof name === 'object') {
+      // Handle event maps.
+      if (callback !== void 0 && 'context' in opts && opts.context === void 0) opts.context = callback;
+      for (names = _.keys(name); i < names.length ; i++) {
+        events = eventsApi(iteratee, events, names[i], name[names[i]], opts);
+      }
+    } else if (name && eventSplitter.test(name)) {
+      // Handle space-separated event names by delegating them individually.
+      for (names = name.split(eventSplitter); i < names.length; i++) {
+        events = iteratee(events, names[i], callback, opts);
+      }
+    } else {
+      // Finally, standard events.
+      events = iteratee(events, name, callback, opts);
+    }
+    return events;
+  };
+
+  // Bind an event to a `callback` function. Passing `"all"` will bind
+  // the callback to all events fired.
+  Events.on = function(name, callback, context) {
+    return internalOn(this, name, callback, context);
+  };
+
+  // Guard the `listening` argument from the public API.
+  var internalOn = function(obj, name, callback, context, listening) {
+    obj._events = eventsApi(onApi, obj._events || {}, name, callback, {
+      context: context,
+      ctx: obj,
+      listening: listening
+    });
+
+    if (listening) {
+      var listeners = obj._listeners || (obj._listeners = {});
+      listeners[listening.id] = listening;
+    }
+
+    return obj;
+  };
+
+  // Inversion-of-control versions of `on`. Tell *this* object to listen to
+  // an event in another object... keeping track of what it's listening to
+  // for easier unbinding later.
+  Events.listenTo = function(obj, name, callback) {
+    if (!obj) return this;
+    var id = obj._listenId || (obj._listenId = _.uniqueId('l'));
+    var listeningTo = this._listeningTo || (this._listeningTo = {});
+    var listening = listeningTo[id];
+
+    // This object is not listening to any other events on `obj` yet.
+    // Setup the necessary references to track the listening callbacks.
+    if (!listening) {
+      var thisId = this._listenId || (this._listenId = _.uniqueId('l'));
+      listening = listeningTo[id] = {obj: obj, objId: id, id: thisId, listeningTo: listeningTo, count: 0};
+    }
+
+    // Bind callbacks on obj, and keep track of them on listening.
+    internalOn(obj, name, callback, this, listening);
+    return this;
+  };
+
+  // The reducing API that adds a callback to the `events` object.
+  var onApi = function(events, name, callback, options) {
+    if (callback) {
+      var handlers = events[name] || (events[name] = []);
+      var context = options.context, ctx = options.ctx, listening = options.listening;
+      if (listening) listening.count++;
+
+      handlers.push({callback: callback, context: context, ctx: context || ctx, listening: listening});
+    }
+    return events;
+  };
+
+  // Remove one or many callbacks. If `context` is null, removes all
+  // callbacks with that function. If `callback` is null, removes all
+  // callbacks for the event. If `name` is null, removes all bound
+  // callbacks for all events.
+  Events.off = function(name, callback, context) {
+    if (!this._events) return this;
+    this._events = eventsApi(offApi, this._events, name, callback, {
+      context: context,
+      listeners: this._listeners
+    });
+    return this;
+  };
+
+  // Tell this object to stop listening to either specific events ... or
+  // to every object it's currently listening to.
+  Events.stopListening = function(obj, name, callback) {
+    var listeningTo = this._listeningTo;
+    if (!listeningTo) return this;
+
+    var ids = obj ? [obj._listenId] : _.keys(listeningTo);
+
+    for (var i = 0; i < ids.length; i++) {
+      var listening = listeningTo[ids[i]];
+
+      // If listening doesn't exist, this object is not currently
+      // listening to obj. Break out early.
+      if (!listening) break;
+
+      listening.obj.off(name, callback, this);
+    }
+
+    return this;
+  };
+
+  // The reducing API that removes a callback from the `events` object.
+  var offApi = function(events, name, callback, options) {
+    if (!events) return;
+
+    var i = 0, listening;
+    var context = options.context, listeners = options.listeners;
+
+    // Delete all events listeners and "drop" events.
+    if (!name && !callback && !context) {
+      var ids = _.keys(listeners);
+      for (; i < ids.length; i++) {
+        listening = listeners[ids[i]];
+        delete listeners[listening.id];
+        delete listening.listeningTo[listening.objId];
+      }
+      return;
+    }
+
+    var names = name ? [name] : _.keys(events);
+    for (; i < names.length; i++) {
+      name = names[i];
+      var handlers = events[name];
+
+      // Bail out if there are no events stored.
+      if (!handlers) break;
+
+      // Replace events if there are any remaining.  Otherwise, clean up.
+      var remaining = [];
+      for (var j = 0; j < handlers.length; j++) {
+        var handler = handlers[j];
+        if (
+          callback && callback !== handler.callback &&
+            callback !== handler.callback._callback ||
+              context && context !== handler.context
+        ) {
+          remaining.push(handler);
+        } else {
+          listening = handler.listening;
+          if (listening && --listening.count === 0) {
+            delete listeners[listening.id];
+            delete listening.listeningTo[listening.objId];
+          }
+        }
+      }
+
+      // Update tail event if the list has any events.  Otherwise, clean up.
+      if (remaining.length) {
+        events[name] = remaining;
+      } else {
+        delete events[name];
+      }
+    }
+    return events;
+  };
+
+  // Bind an event to only be triggered a single time. After the first time
+  // the callback is invoked, its listener will be removed. If multiple events
+  // are passed in using the space-separated syntax, the handler will fire
+  // once for each event, not once for a combination of all events.
+  Events.once = function(name, callback, context) {
+    // Map the event into a `{event: once}` object.
+    var events = eventsApi(onceMap, {}, name, callback, _.bind(this.off, this));
+    if (typeof name === 'string' && context == null) callback = void 0;
+    return this.on(events, callback, context);
+  };
+
+  // Inversion-of-control versions of `once`.
+  Events.listenToOnce = function(obj, name, callback) {
+    // Map the event into a `{event: once}` object.
+    var events = eventsApi(onceMap, {}, name, callback, _.bind(this.stopListening, this, obj));
+    return this.listenTo(obj, events);
+  };
+
+  // Reduces the event callbacks into a map of `{event: onceWrapper}`.
+  // `offer` unbinds the `onceWrapper` after it has been called.
+  var onceMap = function(map, name, callback, offer) {
+    if (callback) {
+      var once = map[name] = _.once(function() {
+        offer(name, once);
+        callback.apply(this, arguments);
+      });
+      once._callback = callback;
+    }
+    return map;
+  };
+
+  // Trigger one or many events, firing all bound callbacks. Callbacks are
+  // passed the same arguments as `trigger` is, apart from the event name
+  // (unless you're listening on `"all"`, which will cause your callback to
+  // receive the true name of the event as the first argument).
+  Events.trigger = function(name) {
+    if (!this._events) return this;
+
+    var length = Math.max(0, arguments.length - 1);
+    var args = Array(length);
+    for (var i = 0; i < length; i++) args[i] = arguments[i + 1];
+
+    eventsApi(triggerApi, this._events, name, void 0, args);
+    return this;
+  };
+
+  // Handles triggering the appropriate event callbacks.
+  var triggerApi = function(objEvents, name, callback, args) {
+    if (objEvents) {
+      var events = objEvents[name];
+      var allEvents = objEvents.all;
+      if (events && allEvents) allEvents = allEvents.slice();
+      if (events) triggerEvents(events, args);
+      if (allEvents) triggerEvents(allEvents, [name].concat(args));
+    }
+    return objEvents;
+  };
+
+  // A difficult-to-believe, but optimized internal dispatch function for
+  // triggering events. Tries to keep the usual cases speedy (most internal
+  // Backbone events have 3 arguments).
+  var triggerEvents = function(events, args) {
+    var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
+    switch (args.length) {
+      case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
+      case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
+      case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
+      case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
+      default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args); return;
+    }
+  };
+
+  // Aliases for backwards compatibility.
+  Events.bind   = Events.on;
+  Events.unbind = Events.off;
+
+  // Allow the `Backbone` object to serve as a global event bus, for folks who
+  // want global "pubsub" in a convenient place.
+  _.extend(Backbone, Events);
+
+  // Backbone.Model
+  // --------------
+
+  // Backbone **Models** are the basic data object in the framework --
+  // frequently representing a row in a table in a database on your server.
+  // A discrete chunk of data and a bunch of useful, related methods for
+  // performing computations and transformations on that data.
+
+  // Create a new model with the specified attributes. A client id (`cid`)
+  // is automatically generated and assigned for you.
+  var Model = Backbone.Model = function(attributes, options) {
+    var attrs = attributes || {};
+    options || (options = {});
+    this.cid = _.uniqueId(this.cidPrefix);
+    this.attributes = {};
+    if (options.collection) this.collection = options.collection;
+    if (options.parse) attrs = this.parse(attrs, options) || {};
+    var defaults = _.result(this, 'defaults');
+    attrs = _.defaults(_.extend({}, defaults, attrs), defaults);
+    this.set(attrs, options);
+    this.changed = {};
+    this.initialize.apply(this, arguments);
+  };
+
+  // Attach all inheritable methods to the Model prototype.
+  _.extend(Model.prototype, Events, {
+
+    // A hash of attributes whose current and previous value differ.
+    changed: null,
+
+    // The value returned during the last failed validation.
+    validationError: null,
+
+    // The default name for the JSON `id` attribute is `"id"`. MongoDB and
+    // CouchDB users may want to set this to `"_id"`.
+    idAttribute: 'id',
+
+    // The prefix is used to create the client id which is used to identify models locally.
+    // You may want to override this if you're experiencing name clashes with model ids.
+    cidPrefix: 'c',
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // Return a copy of the model's `attributes` object.
+    toJSON: function(options) {
+      return _.clone(this.attributes);
+    },
+
+    // Proxy `Backbone.sync` by default -- but override this if you need
+    // custom syncing semantics for *this* particular model.
+    sync: function() {
+      return Backbone.sync.apply(this, arguments);
+    },
+
+    // Get the value of an attribute.
+    get: function(attr) {
+      return this.attributes[attr];
+    },
+
+    // Get the HTML-escaped value of an attribute.
+    escape: function(attr) {
+      return _.escape(this.get(attr));
+    },
+
+    // Returns `true` if the attribute contains a value that is not null
+    // or undefined.
+    has: function(attr) {
+      return this.get(attr) != null;
+    },
+
+    // Special-cased proxy to underscore's `_.matches` method.
+    matches: function(attrs) {
+      return !!_.iteratee(attrs, this)(this.attributes);
+    },
+
+    // Set a hash of model attributes on the object, firing `"change"`. This is
+    // the core primitive operation of a model, updating the data and notifying
+    // anyone who needs to know about the change in state. The heart of the beast.
+    set: function(key, val, options) {
+      if (key == null) return this;
+
+      // Handle both `"key", value` and `{key: value}` -style arguments.
+      var attrs;
+      if (typeof key === 'object') {
+        attrs = key;
+        options = val;
+      } else {
+        (attrs = {})[key] = val;
+      }
+
+      options || (options = {});
+
+      // Run validation.
+      if (!this._validate(attrs, options)) return false;
+
+      // Extract attributes and options.
+      var unset      = options.unset;
+      var silent     = options.silent;
+      var changes    = [];
+      var changing   = this._changing;
+      this._changing = true;
+
+      if (!changing) {
+        this._previousAttributes = _.clone(this.attributes);
+        this.changed = {};
+      }
+
+      var current = this.attributes;
+      var changed = this.changed;
+      var prev    = this._previousAttributes;
+
+      // For each `set` attribute, update or delete the current value.
+      for (var attr in attrs) {
+        val = attrs[attr];
+        if (!_.isEqual(current[attr], val)) changes.push(attr);
+        if (!_.isEqual(prev[attr], val)) {
+          changed[attr] = val;
+        } else {
+          delete changed[attr];
+        }
+        unset ? delete current[attr] : current[attr] = val;
+      }
+
+      // Update the `id`.
+      if (this.idAttribute in attrs) this.id = this.get(this.idAttribute);
+
+      // Trigger all relevant attribute changes.
+      if (!silent) {
+        if (changes.length) this._pending = options;
+        for (var i = 0; i < changes.length; i++) {
+          this.trigger('change:' + changes[i], this, current[changes[i]], options);
+        }
+      }
+
+      // You might be wondering why there's a `while` loop here. Changes can
+      // be recursively nested within `"change"` events.
+      if (changing) return this;
+      if (!silent) {
+        while (this._pending) {
+          options = this._pending;
+          this._pending = false;
+          this.trigger('change', this, options);
+        }
+      }
+      this._pending = false;
+      this._changing = false;
+      return this;
+    },
+
+    // Remove an attribute from the model, firing `"change"`. `unset` is a noop
+    // if the attribute doesn't exist.
+    unset: function(attr, options) {
+      return this.set(attr, void 0, _.extend({}, options, {unset: true}));
+    },
+
+    // Clear all attributes on the model, firing `"change"`.
+    clear: function(options) {
+      var attrs = {};
+      for (var key in this.attributes) attrs[key] = void 0;
+      return this.set(attrs, _.extend({}, options, {unset: true}));
+    },
+
+    // Determine if the model has changed since the last `"change"` event.
+    // If you specify an attribute name, determine if that attribute has changed.
+    hasChanged: function(attr) {
+      if (attr == null) return !_.isEmpty(this.changed);
+      return _.has(this.changed, attr);
+    },
+
+    // Return an object containing all the attributes that have changed, or
+    // false if there are no changed attributes. Useful for determining what
+    // parts of a view need to be updated and/or what attributes need to be
+    // persisted to the server. Unset attributes will be set to undefined.
+    // You can also pass an attributes object to diff against the model,
+    // determining if there *would be* a change.
+    changedAttributes: function(diff) {
+      if (!diff) return this.hasChanged() ? _.clone(this.changed) : false;
+      var old = this._changing ? this._previousAttributes : this.attributes;
+      var changed = {};
+      for (var attr in diff) {
+        var val = diff[attr];
+        if (_.isEqual(old[attr], val)) continue;
+        changed[attr] = val;
+      }
+      return _.size(changed) ? changed : false;
+    },
+
+    // Get the previous value of an attribute, recorded at the time the last
+    // `"change"` event was fired.
+    previous: function(attr) {
+      if (attr == null || !this._previousAttributes) return null;
+      return this._previousAttributes[attr];
+    },
+
+    // Get all of the attributes of the model at the time of the previous
+    // `"change"` event.
+    previousAttributes: function() {
+      return _.clone(this._previousAttributes);
+    },
+
+    // Fetch the model from the server, merging the response with the model's
+    // local attributes. Any changed attributes will trigger a "change" event.
+    fetch: function(options) {
+      options = _.extend({parse: true}, options);
+      var model = this;
+      var success = options.success;
+      options.success = function(resp) {
+        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+        if (!model.set(serverAttrs, options)) return false;
+        if (success) success.call(options.context, model, resp, options);
+        model.trigger('sync', model, resp, options);
+      };
+      wrapError(this, options);
+      return this.sync('read', this, options);
+    },
+
+    // Set a hash of model attributes, and sync the model to the server.
+    // If the server returns an attributes hash that differs, the model's
+    // state will be `set` again.
+    save: function(key, val, options) {
+      // Handle both `"key", value` and `{key: value}` -style arguments.
+      var attrs;
+      if (key == null || typeof key === 'object') {
+        attrs = key;
+        options = val;
+      } else {
+        (attrs = {})[key] = val;
+      }
+
+      options = _.extend({validate: true, parse: true}, options);
+      var wait = options.wait;
+
+      // If we're not waiting and attributes exist, save acts as
+      // `set(attr).save(null, opts)` with validation. Otherwise, check if
+      // the model will be valid when the attributes, if any, are set.
+      if (attrs && !wait) {
+        if (!this.set(attrs, options)) return false;
+      } else if (!this._validate(attrs, options)) {
+        return false;
+      }
+
+      // After a successful server-side save, the client is (optionally)
+      // updated with the server-side state.
+      var model = this;
+      var success = options.success;
+      var attributes = this.attributes;
+      options.success = function(resp) {
+        // Ensure attributes are restored during synchronous saves.
+        model.attributes = attributes;
+        var serverAttrs = options.parse ? model.parse(resp, options) : resp;
+        if (wait) serverAttrs = _.extend({}, attrs, serverAttrs);
+        if (serverAttrs && !model.set(serverAttrs, options)) return false;
+        if (success) success.call(options.context, model, resp, options);
+        model.trigger('sync', model, resp, options);
+      };
+      wrapError(this, options);
+
+      // Set temporary attributes if `{wait: true}` to properly find new ids.
+      if (attrs && wait) this.attributes = _.extend({}, attributes, attrs);
+
+      var method = this.isNew() ? 'create' : (options.patch ? 'patch' : 'update');
+      if (method === 'patch' && !options.attrs) options.attrs = attrs;
+      var xhr = this.sync(method, this, options);
+
+      // Restore attributes.
+      this.attributes = attributes;
+
+      return xhr;
+    },
+
+    // Destroy this model on the server if it was already persisted.
+    // Optimistically removes the model from its collection, if it has one.
+    // If `wait: true` is passed, waits for the server to respond before removal.
+    destroy: function(options) {
+      options = options ? _.clone(options) : {};
+      var model = this;
+      var success = options.success;
+      var wait = options.wait;
+
+      var destroy = function() {
+        model.stopListening();
+        model.trigger('destroy', model, model.collection, options);
+      };
+
+      options.success = function(resp) {
+        if (wait) destroy();
+        if (success) success.call(options.context, model, resp, options);
+        if (!model.isNew()) model.trigger('sync', model, resp, options);
+      };
+
+      var xhr = false;
+      if (this.isNew()) {
+        _.defer(options.success);
+      } else {
+        wrapError(this, options);
+        xhr = this.sync('delete', this, options);
+      }
+      if (!wait) destroy();
+      return xhr;
+    },
+
+    // Default URL for the model's representation on the server -- if you're
+    // using Backbone's restful methods, override this to change the endpoint
+    // that will be called.
+    url: function() {
+      var base =
+        _.result(this, 'urlRoot') ||
+        _.result(this.collection, 'url') ||
+        urlError();
+      if (this.isNew()) return base;
+      var id = this.get(this.idAttribute);
+      return base.replace(/[^\/]$/, '$&/') + encodeURIComponent(id);
+    },
+
+    // **parse** converts a response into the hash of attributes to be `set` on
+    // the model. The default implementation is just to pass the response along.
+    parse: function(resp, options) {
+      return resp;
+    },
+
+    // Create a new model with identical attributes to this one.
+    clone: function() {
+      return new this.constructor(this.attributes);
+    },
+
+    // A model is new if it has never been saved to the server, and lacks an id.
+    isNew: function() {
+      return !this.has(this.idAttribute);
+    },
+
+    // Check if the model is currently in a valid state.
+    isValid: function(options) {
+      return this._validate({}, _.extend({}, options, {validate: true}));
+    },
+
+    // Run validation against the next complete set of model attributes,
+    // returning `true` if all is well. Otherwise, fire an `"invalid"` event.
+    _validate: function(attrs, options) {
+      if (!options.validate || !this.validate) return true;
+      attrs = _.extend({}, this.attributes, attrs);
+      var error = this.validationError = this.validate(attrs, options) || null;
+      if (!error) return true;
+      this.trigger('invalid', this, error, _.extend(options, {validationError: error}));
+      return false;
+    }
+
+  });
+
+  // Underscore methods that we want to implement on the Model, mapped to the
+  // number of arguments they take.
+  var modelMethods = {keys: 1, values: 1, pairs: 1, invert: 1, pick: 0,
+      omit: 0, chain: 1, isEmpty: 1};
+
+  // Mix in each Underscore method as a proxy to `Model#attributes`.
+  addUnderscoreMethods(Model, modelMethods, 'attributes');
+
+  // Backbone.Collection
+  // -------------------
+
+  // If models tend to represent a single row of data, a Backbone Collection is
+  // more analogous to a table full of data ... or a small slice or page of that
+  // table, or a collection of rows that belong together for a particular reason
+  // -- all of the messages in this particular folder, all of the documents
+  // belonging to this particular author, and so on. Collections maintain
+  // indexes of their models, both in order, and for lookup by `id`.
+
+  // Create a new **Collection**, perhaps to contain a specific type of `model`.
+  // If a `comparator` is specified, the Collection will maintain
+  // its models in sort order, as they're added and removed.
+  var Collection = Backbone.Collection = function(models, options) {
+    options || (options = {});
+    if (options.model) this.model = options.model;
+    if (options.comparator !== void 0) this.comparator = options.comparator;
+    this._reset();
+    this.initialize.apply(this, arguments);
+    if (models) this.reset(models, _.extend({silent: true}, options));
+  };
+
+  // Default options for `Collection#set`.
+  var setOptions = {add: true, remove: true, merge: true};
+  var addOptions = {add: true, remove: false};
+
+  // Splices `insert` into `array` at index `at`.
+  var splice = function(array, insert, at) {
+    at = Math.min(Math.max(at, 0), array.length);
+    var tail = Array(array.length - at);
+    var length = insert.length;
+    var i;
+    for (i = 0; i < tail.length; i++) tail[i] = array[i + at];
+    for (i = 0; i < length; i++) array[i + at] = insert[i];
+    for (i = 0; i < tail.length; i++) array[i + length + at] = tail[i];
+  };
+
+  // Define the Collection's inheritable methods.
+  _.extend(Collection.prototype, Events, {
+
+    // The default model for a collection is just a **Backbone.Model**.
+    // This should be overridden in most cases.
+    model: Model,
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // The JSON representation of a Collection is an array of the
+    // models' attributes.
+    toJSON: function(options) {
+      return this.map(function(model) { return model.toJSON(options); });
+    },
+
+    // Proxy `Backbone.sync` by default.
+    sync: function() {
+      return Backbone.sync.apply(this, arguments);
+    },
+
+    // Add a model, or list of models to the set. `models` may be Backbone
+    // Models or raw JavaScript objects to be converted to Models, or any
+    // combination of the two.
+    add: function(models, options) {
+      return this.set(models, _.extend({merge: false}, options, addOptions));
+    },
+
+    // Remove a model, or a list of models from the set.
+    remove: function(models, options) {
+      options = _.extend({}, options);
+      var singular = !_.isArray(models);
+      models = singular ? [models] : models.slice();
+      var removed = this._removeModels(models, options);
+      if (!options.silent && removed.length) {
+        options.changes = {added: [], merged: [], removed: removed};
+        this.trigger('update', this, options);
+      }
+      return singular ? removed[0] : removed;
+    },
+
+    // Update a collection by `set`-ing a new list of models, adding new ones,
+    // removing models that are no longer present, and merging models that
+    // already exist in the collection, as necessary. Similar to **Model#set**,
+    // the core operation for updating the data contained by the collection.
+    set: function(models, options) {
+      if (models == null) return;
+
+      options = _.extend({}, setOptions, options);
+      if (options.parse && !this._isModel(models)) {
+        models = this.parse(models, options) || [];
+      }
+
+      var singular = !_.isArray(models);
+      models = singular ? [models] : models.slice();
+
+      var at = options.at;
+      if (at != null) at = +at;
+      if (at > this.length) at = this.length;
+      if (at < 0) at += this.length + 1;
+
+      var set = [];
+      var toAdd = [];
+      var toMerge = [];
+      var toRemove = [];
+      var modelMap = {};
+
+      var add = options.add;
+      var merge = options.merge;
+      var remove = options.remove;
+
+      var sort = false;
+      var sortable = this.comparator && at == null && options.sort !== false;
+      var sortAttr = _.isString(this.comparator) ? this.comparator : null;
+
+      // Turn bare objects into model references, and prevent invalid models
+      // from being added.
+      var model, i;
+      for (i = 0; i < models.length; i++) {
+        model = models[i];
+
+        // If a duplicate is found, prevent it from being added and
+        // optionally merge it into the existing model.
+        var existing = this.get(model);
+        if (existing) {
+          if (merge && model !== existing) {
+            var attrs = this._isModel(model) ? model.attributes : model;
+            if (options.parse) attrs = existing.parse(attrs, options);
+            existing.set(attrs, options);
+            toMerge.push(existing);
+            if (sortable && !sort) sort = existing.hasChanged(sortAttr);
+          }
+          if (!modelMap[existing.cid]) {
+            modelMap[existing.cid] = true;
+            set.push(existing);
+          }
+          models[i] = existing;
+
+        // If this is a new, valid model, push it to the `toAdd` list.
+        } else if (add) {
+          model = models[i] = this._prepareModel(model, options);
+          if (model) {
+            toAdd.push(model);
+            this._addReference(model, options);
+            modelMap[model.cid] = true;
+            set.push(model);
+          }
+        }
+      }
+
+      // Remove stale models.
+      if (remove) {
+        for (i = 0; i < this.length; i++) {
+          model = this.models[i];
+          if (!modelMap[model.cid]) toRemove.push(model);
+        }
+        if (toRemove.length) this._removeModels(toRemove, options);
+      }
+
+      // See if sorting is needed, update `length` and splice in new models.
+      var orderChanged = false;
+      var replace = !sortable && add && remove;
+      if (set.length && replace) {
+        orderChanged = this.length !== set.length || _.some(this.models, function(m, index) {
+          return m !== set[index];
+        });
+        this.models.length = 0;
+        splice(this.models, set, 0);
+        this.length = this.models.length;
+      } else if (toAdd.length) {
+        if (sortable) sort = true;
+        splice(this.models, toAdd, at == null ? this.length : at);
+        this.length = this.models.length;
+      }
+
+      // Silently sort the collection if appropriate.
+      if (sort) this.sort({silent: true});
+
+      // Unless silenced, it's time to fire all appropriate add/sort/update events.
+      if (!options.silent) {
+        for (i = 0; i < toAdd.length; i++) {
+          if (at != null) options.index = at + i;
+          model = toAdd[i];
+          model.trigger('add', model, this, options);
+        }
+        if (sort || orderChanged) this.trigger('sort', this, options);
+        if (toAdd.length || toRemove.length || toMerge.length) {
+          options.changes = {
+            added: toAdd,
+            removed: toRemove,
+            merged: toMerge
+          };
+          this.trigger('update', this, options);
+        }
+      }
+
+      // Return the added (or merged) model (or models).
+      return singular ? models[0] : models;
+    },
+
+    // When you have more items than you want to add or remove individually,
+    // you can reset the entire set with a new list of models, without firing
+    // any granular `add` or `remove` events. Fires `reset` when finished.
+    // Useful for bulk operations and optimizations.
+    reset: function(models, options) {
+      options = options ? _.clone(options) : {};
+      for (var i = 0; i < this.models.length; i++) {
+        this._removeReference(this.models[i], options);
+      }
+      options.previousModels = this.models;
+      this._reset();
+      models = this.add(models, _.extend({silent: true}, options));
+      if (!options.silent) this.trigger('reset', this, options);
+      return models;
+    },
+
+    // Add a model to the end of the collection.
+    push: function(model, options) {
+      return this.add(model, _.extend({at: this.length}, options));
+    },
+
+    // Remove a model from the end of the collection.
+    pop: function(options) {
+      var model = this.at(this.length - 1);
+      return this.remove(model, options);
+    },
+
+    // Add a model to the beginning of the collection.
+    unshift: function(model, options) {
+      return this.add(model, _.extend({at: 0}, options));
+    },
+
+    // Remove a model from the beginning of the collection.
+    shift: function(options) {
+      var model = this.at(0);
+      return this.remove(model, options);
+    },
+
+    // Slice out a sub-array of models from the collection.
+    slice: function() {
+      return slice.apply(this.models, arguments);
+    },
+
+    // Get a model from the set by id, cid, model object with id or cid
+    // properties, or an attributes object that is transformed through modelId.
+    get: function(obj) {
+      if (obj == null) return void 0;
+      return this._byId[obj] ||
+        this._byId[this.modelId(obj.attributes || obj)] ||
+        obj.cid && this._byId[obj.cid];
+    },
+
+    // Returns `true` if the model is in the collection.
+    has: function(obj) {
+      return this.get(obj) != null;
+    },
+
+    // Get the model at the given index.
+    at: function(index) {
+      if (index < 0) index += this.length;
+      return this.models[index];
+    },
+
+    // Return models with matching attributes. Useful for simple cases of
+    // `filter`.
+    where: function(attrs, first) {
+      return this[first ? 'find' : 'filter'](attrs);
+    },
+
+    // Return the first model with matching attributes. Useful for simple cases
+    // of `find`.
+    findWhere: function(attrs) {
+      return this.where(attrs, true);
+    },
+
+    // Force the collection to re-sort itself. You don't need to call this under
+    // normal circumstances, as the set will maintain sort order as each item
+    // is added.
+    sort: function(options) {
+      var comparator = this.comparator;
+      if (!comparator) throw new Error('Cannot sort a set without a comparator');
+      options || (options = {});
+
+      var length = comparator.length;
+      if (_.isFunction(comparator)) comparator = _.bind(comparator, this);
+
+      // Run sort based on type of `comparator`.
+      if (length === 1 || _.isString(comparator)) {
+        this.models = this.sortBy(comparator);
+      } else {
+        this.models.sort(comparator);
+      }
+      if (!options.silent) this.trigger('sort', this, options);
+      return this;
+    },
+
+    // Pluck an attribute from each model in the collection.
+    pluck: function(attr) {
+      return this.map(attr + '');
+    },
+
+    // Fetch the default set of models for this collection, resetting the
+    // collection when they arrive. If `reset: true` is passed, the response
+    // data will be passed through the `reset` method instead of `set`.
+    fetch: function(options) {
+      options = _.extend({parse: true}, options);
+      var success = options.success;
+      var collection = this;
+      options.success = function(resp) {
+        var method = options.reset ? 'reset' : 'set';
+        collection[method](resp, options);
+        if (success) success.call(options.context, collection, resp, options);
+        collection.trigger('sync', collection, resp, options);
+      };
+      wrapError(this, options);
+      return this.sync('read', this, options);
+    },
+
+    // Create a new instance of a model in this collection. Add the model to the
+    // collection immediately, unless `wait: true` is passed, in which case we
+    // wait for the server to agree.
+    create: function(model, options) {
+      options = options ? _.clone(options) : {};
+      var wait = options.wait;
+      model = this._prepareModel(model, options);
+      if (!model) return false;
+      if (!wait) this.add(model, options);
+      var collection = this;
+      var success = options.success;
+      options.success = function(m, resp, callbackOpts) {
+        if (wait) collection.add(m, callbackOpts);
+        if (success) success.call(callbackOpts.context, m, resp, callbackOpts);
+      };
+      model.save(null, options);
+      return model;
+    },
+
+    // **parse** converts a response into a list of models to be added to the
+    // collection. The default implementation is just to pass it through.
+    parse: function(resp, options) {
+      return resp;
+    },
+
+    // Create a new collection with an identical list of models as this one.
+    clone: function() {
+      return new this.constructor(this.models, {
+        model: this.model,
+        comparator: this.comparator
+      });
+    },
+
+    // Define how to uniquely identify models in the collection.
+    modelId: function(attrs) {
+      return attrs[this.model.prototype.idAttribute || 'id'];
+    },
+
+    // Private method to reset all internal state. Called when the collection
+    // is first initialized or reset.
+    _reset: function() {
+      this.length = 0;
+      this.models = [];
+      this._byId  = {};
+    },
+
+    // Prepare a hash of attributes (or other model) to be added to this
+    // collection.
+    _prepareModel: function(attrs, options) {
+      if (this._isModel(attrs)) {
+        if (!attrs.collection) attrs.collection = this;
+        return attrs;
+      }
+      options = options ? _.clone(options) : {};
+      options.collection = this;
+      var model = new this.model(attrs, options);
+      if (!model.validationError) return model;
+      this.trigger('invalid', this, model.validationError, options);
+      return false;
+    },
+
+    // Internal method called by both remove and set.
+    _removeModels: function(models, options) {
+      var removed = [];
+      for (var i = 0; i < models.length; i++) {
+        var model = this.get(models[i]);
+        if (!model) continue;
+
+        var index = this.indexOf(model);
+        this.models.splice(index, 1);
+        this.length--;
+
+        // Remove references before triggering 'remove' event to prevent an
+        // infinite loop. #3693
+        delete this._byId[model.cid];
+        var id = this.modelId(model.attributes);
+        if (id != null) delete this._byId[id];
+
+        if (!options.silent) {
+          options.index = index;
+          model.trigger('remove', model, this, options);
+        }
+
+        removed.push(model);
+        this._removeReference(model, options);
+      }
+      return removed;
+    },
+
+    // Method for checking whether an object should be considered a model for
+    // the purposes of adding to the collection.
+    _isModel: function(model) {
+      return model instanceof Model;
+    },
+
+    // Internal method to create a model's ties to a collection.
+    _addReference: function(model, options) {
+      this._byId[model.cid] = model;
+      var id = this.modelId(model.attributes);
+      if (id != null) this._byId[id] = model;
+      model.on('all', this._onModelEvent, this);
+    },
+
+    // Internal method to sever a model's ties to a collection.
+    _removeReference: function(model, options) {
+      delete this._byId[model.cid];
+      var id = this.modelId(model.attributes);
+      if (id != null) delete this._byId[id];
+      if (this === model.collection) delete model.collection;
+      model.off('all', this._onModelEvent, this);
+    },
+
+    // Internal method called every time a model in the set fires an event.
+    // Sets need to update their indexes when models change ids. All other
+    // events simply proxy through. "add" and "remove" events that originate
+    // in other collections are ignored.
+    _onModelEvent: function(event, model, collection, options) {
+      if (model) {
+        if ((event === 'add' || event === 'remove') && collection !== this) return;
+        if (event === 'destroy') this.remove(model, options);
+        if (event === 'change') {
+          var prevId = this.modelId(model.previousAttributes());
+          var id = this.modelId(model.attributes);
+          if (prevId !== id) {
+            if (prevId != null) delete this._byId[prevId];
+            if (id != null) this._byId[id] = model;
+          }
+        }
+      }
+      this.trigger.apply(this, arguments);
+    }
+
+  });
+
+  // Underscore methods that we want to implement on the Collection.
+  // 90% of the core usefulness of Backbone Collections is actually implemented
+  // right here:
+  var collectionMethods = {forEach: 3, each: 3, map: 3, collect: 3, reduce: 0,
+      foldl: 0, inject: 0, reduceRight: 0, foldr: 0, find: 3, detect: 3, filter: 3,
+      select: 3, reject: 3, every: 3, all: 3, some: 3, any: 3, include: 3, includes: 3,
+      contains: 3, invoke: 0, max: 3, min: 3, toArray: 1, size: 1, first: 3,
+      head: 3, take: 3, initial: 3, rest: 3, tail: 3, drop: 3, last: 3,
+      without: 0, difference: 0, indexOf: 3, shuffle: 1, lastIndexOf: 3,
+      isEmpty: 1, chain: 1, sample: 3, partition: 3, groupBy: 3, countBy: 3,
+      sortBy: 3, indexBy: 3, findIndex: 3, findLastIndex: 3};
+
+  // Mix in each Underscore method as a proxy to `Collection#models`.
+  addUnderscoreMethods(Collection, collectionMethods, 'models');
+
+  // Backbone.View
+  // -------------
+
+  // Backbone Views are almost more convention than they are actual code. A View
+  // is simply a JavaScript object that represents a logical chunk of UI in the
+  // DOM. This might be a single item, an entire list, a sidebar or panel, or
+  // even the surrounding frame which wraps your whole app. Defining a chunk of
+  // UI as a **View** allows you to define your DOM events declaratively, without
+  // having to worry about render order ... and makes it easy for the view to
+  // react to specific changes in the state of your models.
+
+  // Creating a Backbone.View creates its initial element outside of the DOM,
+  // if an existing element is not provided...
+  var View = Backbone.View = function(options) {
+    this.cid = _.uniqueId('view');
+    _.extend(this, _.pick(options, viewOptions));
+    this._ensureElement();
+    this.initialize.apply(this, arguments);
+  };
+
+  // Cached regex to split keys for `delegate`.
+  var delegateEventSplitter = /^(\S+)\s*(.*)$/;
+
+  // List of view options to be set as properties.
+  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];
+
+  // Set up all inheritable **Backbone.View** properties and methods.
+  _.extend(View.prototype, Events, {
+
+    // The default `tagName` of a View's element is `"div"`.
+    tagName: 'div',
+
+    // jQuery delegate for element lookup, scoped to DOM elements within the
+    // current view. This should be preferred to global lookups where possible.
+    $: function(selector) {
+      return this.$el.find(selector);
+    },
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // **render** is the core function that your view should override, in order
+    // to populate its element (`this.el`), with the appropriate HTML. The
+    // convention is for **render** to always return `this`.
+    render: function() {
+      return this;
+    },
+
+    // Remove this view by taking the element out of the DOM, and removing any
+    // applicable Backbone.Events listeners.
+    remove: function() {
+      this._removeElement();
+      this.stopListening();
+      return this;
+    },
+
+    // Remove this view's element from the document and all event listeners
+    // attached to it. Exposed for subclasses using an alternative DOM
+    // manipulation API.
+    _removeElement: function() {
+      this.$el.remove();
+    },
+
+    // Change the view's element (`this.el` property) and re-delegate the
+    // view's events on the new element.
+    setElement: function(element) {
+      this.undelegateEvents();
+      this._setElement(element);
+      this.delegateEvents();
+      return this;
+    },
+
+    // Creates the `this.el` and `this.$el` references for this view using the
+    // given `el`. `el` can be a CSS selector or an HTML string, a jQuery
+    // context or an element. Subclasses can override this to utilize an
+    // alternative DOM manipulation API and are only required to set the
+    // `this.el` property.
+    _setElement: function(el) {
+      this.$el = el instanceof Backbone.$ ? el : Backbone.$(el);
+      this.el = this.$el[0];
+    },
+
+    // Set callbacks, where `this.events` is a hash of
+    //
+    // *{"event selector": "callback"}*
+    //
+    //     {
+    //       'mousedown .title':  'edit',
+    //       'click .button':     'save',
+    //       'click .open':       function(e) { ... }
+    //     }
+    //
+    // pairs. Callbacks will be bound to the view, with `this` set properly.
+    // Uses event delegation for efficiency.
+    // Omitting the selector binds the event to `this.el`.
+    delegateEvents: function(events) {
+      events || (events = _.result(this, 'events'));
+      if (!events) return this;
+      this.undelegateEvents();
+      for (var key in events) {
+        var method = events[key];
+        if (!_.isFunction(method)) method = this[method];
+        if (!method) continue;
+        var match = key.match(delegateEventSplitter);
+        this.delegate(match[1], match[2], _.bind(method, this));
+      }
+      return this;
+    },
+
+    // Add a single event listener to the view's element (or a child element
+    // using `selector`). This only works for delegate-able events: not `focus`,
+    // `blur`, and not `change`, `submit`, and `reset` in Internet Explorer.
+    delegate: function(eventName, selector, listener) {
+      this.$el.on(eventName + '.delegateEvents' + this.cid, selector, listener);
+      return this;
+    },
+
+    // Clears all callbacks previously bound to the view by `delegateEvents`.
+    // You usually don't need to use this, but may wish to if you have multiple
+    // Backbone views attached to the same DOM element.
+    undelegateEvents: function() {
+      if (this.$el) this.$el.off('.delegateEvents' + this.cid);
+      return this;
+    },
+
+    // A finer-grained `undelegateEvents` for removing a single delegated event.
+    // `selector` and `listener` are both optional.
+    undelegate: function(eventName, selector, listener) {
+      this.$el.off(eventName + '.delegateEvents' + this.cid, selector, listener);
+      return this;
+    },
+
+    // Produces a DOM element to be assigned to your view. Exposed for
+    // subclasses using an alternative DOM manipulation API.
+    _createElement: function(tagName) {
+      return document.createElement(tagName);
+    },
+
+    // Ensure that the View has a DOM element to render into.
+    // If `this.el` is a string, pass it through `$()`, take the first
+    // matching element, and re-assign it to `el`. Otherwise, create
+    // an element from the `id`, `className` and `tagName` properties.
+    _ensureElement: function() {
+      if (!this.el) {
+        var attrs = _.extend({}, _.result(this, 'attributes'));
+        if (this.id) attrs.id = _.result(this, 'id');
+        if (this.className) attrs['class'] = _.result(this, 'className');
+        this.setElement(this._createElement(_.result(this, 'tagName')));
+        this._setAttributes(attrs);
+      } else {
+        this.setElement(_.result(this, 'el'));
+      }
+    },
+
+    // Set attributes from a hash on this view's element.  Exposed for
+    // subclasses using an alternative DOM manipulation API.
+    _setAttributes: function(attributes) {
+      this.$el.attr(attributes);
+    }
+
+  });
+
+  // Backbone.sync
+  // -------------
+
+  // Override this function to change the manner in which Backbone persists
+  // models to the server. You will be passed the type of request, and the
+  // model in question. By default, makes a RESTful Ajax request
+  // to the model's `url()`. Some possible customizations could be:
+  //
+  // * Use `setTimeout` to batch rapid-fire updates into a single request.
+  // * Send up the models as XML instead of JSON.
+  // * Persist models via WebSockets instead of Ajax.
+  //
+  // Turn on `Backbone.emulateHTTP` in order to send `PUT` and `DELETE` requests
+  // as `POST`, with a `_method` parameter containing the true HTTP method,
+  // as well as all requests with the body as `application/x-www-form-urlencoded`
+  // instead of `application/json` with the model in a param named `model`.
+  // Useful when interfacing with server-side languages like **PHP** that make
+  // it difficult to read the body of `PUT` requests.
+  Backbone.sync = function(method, model, options) {
+    var type = methodMap[method];
+
+    // Default options, unless specified.
+    _.defaults(options || (options = {}), {
+      emulateHTTP: Backbone.emulateHTTP,
+      emulateJSON: Backbone.emulateJSON
+    });
+
+    // Default JSON-request options.
+    var params = {type: type, dataType: 'json'};
+
+    // Ensure that we have a URL.
+    if (!options.url) {
+      params.url = _.result(model, 'url') || urlError();
+    }
+
+    // Ensure that we have the appropriate request data.
+    if (options.data == null && model && (method === 'create' || method === 'update' || method === 'patch')) {
+      params.contentType = 'application/json';
+      params.data = JSON.stringify(options.attrs || model.toJSON(options));
+    }
+
+    // For older servers, emulate JSON by encoding the request into an HTML-form.
+    if (options.emulateJSON) {
+      params.contentType = 'application/x-www-form-urlencoded';
+      params.data = params.data ? {model: params.data} : {};
+    }
+
+    // For older servers, emulate HTTP by mimicking the HTTP method with `_method`
+    // And an `X-HTTP-Method-Override` header.
+    if (options.emulateHTTP && (type === 'PUT' || type === 'DELETE' || type === 'PATCH')) {
+      params.type = 'POST';
+      if (options.emulateJSON) params.data._method = type;
+      var beforeSend = options.beforeSend;
+      options.beforeSend = function(xhr) {
+        xhr.setRequestHeader('X-HTTP-Method-Override', type);
+        if (beforeSend) return beforeSend.apply(this, arguments);
+      };
+    }
+
+    // Don't process data on a non-GET request.
+    if (params.type !== 'GET' && !options.emulateJSON) {
+      params.processData = false;
+    }
+
+    // Pass along `textStatus` and `errorThrown` from jQuery.
+    var error = options.error;
+    options.error = function(xhr, textStatus, errorThrown) {
+      options.textStatus = textStatus;
+      options.errorThrown = errorThrown;
+      if (error) error.call(options.context, xhr, textStatus, errorThrown);
+    };
+
+    // Make the request, allowing the user to override any Ajax options.
+    var xhr = options.xhr = Backbone.ajax(_.extend(params, options));
+    model.trigger('request', model, xhr, options);
+    return xhr;
+  };
+
+  // Map from CRUD to HTTP for our default `Backbone.sync` implementation.
+  var methodMap = {
+    'create': 'POST',
+    'update': 'PUT',
+    'patch': 'PATCH',
+    'delete': 'DELETE',
+    'read': 'GET'
+  };
+
+  // Set the default implementation of `Backbone.ajax` to proxy through to `$`.
+  // Override this if you'd like to use a different library.
+  Backbone.ajax = function() {
+    return Backbone.$.ajax.apply(Backbone.$, arguments);
+  };
+
+  // Backbone.Router
+  // ---------------
+
+  // Routers map faux-URLs to actions, and fire events when routes are
+  // matched. Creating a new one sets its `routes` hash, if not set statically.
+  var Router = Backbone.Router = function(options) {
+    options || (options = {});
+    if (options.routes) this.routes = options.routes;
+    this._bindRoutes();
+    this.initialize.apply(this, arguments);
+  };
+
+  // Cached regular expressions for matching named param parts and splatted
+  // parts of route strings.
+  var optionalParam = /\((.*?)\)/g;
+  var namedParam    = /(\(\?)?:\w+/g;
+  var splatParam    = /\*\w+/g;
+  var escapeRegExp  = /[\-{}\[\]+?.,\\\^$|#\s]/g;
+
+  // Set up all inheritable **Backbone.Router** properties and methods.
+  _.extend(Router.prototype, Events, {
+
+    // Initialize is an empty function by default. Override it with your own
+    // initialization logic.
+    initialize: function(){},
+
+    // Manually bind a single named route to a callback. For example:
+    //
+    //     this.route('search/:query/p:num', 'search', function(query, num) {
+    //       ...
+    //     });
+    //
+    route: function(route, name, callback) {
+      if (!_.isRegExp(route)) route = this._routeToRegExp(route);
+      if (_.isFunction(name)) {
+        callback = name;
+        name = '';
+      }
+      if (!callback) callback = this[name];
+      var router = this;
+      Backbone.history.route(route, function(fragment) {
+        var args = router._extractParameters(route, fragment);
+        if (router.execute(callback, args, name) !== false) {
+          router.trigger.apply(router, ['route:' + name].concat(args));
+          router.trigger('route', name, args);
+          Backbone.history.trigger('route', router, name, args);
+        }
+      });
+      return this;
+    },
+
+    // Execute a route handler with the provided parameters.  This is an
+    // excellent place to do pre-route setup or post-route cleanup.
+    execute: function(callback, args, name) {
+      if (callback) callback.apply(this, args);
+    },
+
+    // Simple proxy to `Backbone.history` to save a fragment into the history.
+    navigate: function(fragment, options) {
+      Backbone.history.navigate(fragment, options);
+      return this;
+    },
+
+    // Bind all defined routes to `Backbone.history`. We have to reverse the
+    // order of the routes here to support behavior where the most general
+    // routes can be defined at the bottom of the route map.
+    _bindRoutes: function() {
+      if (!this.routes) return;
+      this.routes = _.result(this, 'routes');
+      var route, routes = _.keys(this.routes);
+      while ((route = routes.pop()) != null) {
+        this.route(route, this.routes[route]);
+      }
+    },
+
+    // Convert a route string into a regular expression, suitable for matching
+    // against the current location hash.
+    _routeToRegExp: function(route) {
+      route = route.replace(escapeRegExp, '\\$&')
+                   .replace(optionalParam, '(?:$1)?')
+                   .replace(namedParam, function(match, optional) {
+                     return optional ? match : '([^/?]+)';
+                   })
+                   .replace(splatParam, '([^?]*?)');
+      return new RegExp('^' + route + '(?:\\?([\\s\\S]*))?$');
+    },
+
+    // Given a route, and a URL fragment that it matches, return the array of
+    // extracted decoded parameters. Empty or unmatched parameters will be
+    // treated as `null` to normalize cross-browser behavior.
+    _extractParameters: function(route, fragment) {
+      var params = route.exec(fragment).slice(1);
+      return _.map(params, function(param, i) {
+        // Don't decode the search params.
+        if (i === params.length - 1) return param || null;
+        return param ? decodeURIComponent(param) : null;
+      });
+    }
+
+  });
+
+  // Backbone.History
+  // ----------------
+
+  // Handles cross-browser history management, based on either
+  // [pushState](http://diveintohtml5.info/history.html) and real URLs, or
+  // [onhashchange](https://developer.mozilla.org/en-US/docs/DOM/window.onhashchange)
+  // and URL fragments. If the browser supports neither (old IE, natch),
+  // falls back to polling.
+  var History = Backbone.History = function() {
+    this.handlers = [];
+    this.checkUrl = _.bind(this.checkUrl, this);
+
+    // Ensure that `History` can be used outside of the browser.
+    if (typeof window !== 'undefined') {
+      this.location = window.location;
+      this.history = window.history;
+    }
+  };
+
+  // Cached regex for stripping a leading hash/slash and trailing space.
+  var routeStripper = /^[#\/]|\s+$/g;
+
+  // Cached regex for stripping leading and trailing slashes.
+  var rootStripper = /^\/+|\/+$/g;
+
+  // Cached regex for stripping urls of hash.
+  var pathStripper = /#.*$/;
+
+  // Has the history handling already been started?
+  History.started = false;
+
+  // Set up all inheritable **Backbone.History** properties and methods.
+  _.extend(History.prototype, Events, {
+
+    // The default interval to poll for hash changes, if necessary, is
+    // twenty times a second.
+    interval: 50,
+
+    // Are we at the app root?
+    atRoot: function() {
+      var path = this.location.pathname.replace(/[^\/]$/, '$&/');
+      return path === this.root && !this.getSearch();
+    },
+
+    // Does the pathname match the root?
+    matchRoot: function() {
+      var path = this.decodeFragment(this.location.pathname);
+      var rootPath = path.slice(0, this.root.length - 1) + '/';
+      return rootPath === this.root;
+    },
+
+    // Unicode characters in `location.pathname` are percent encoded so they're
+    // decoded for comparison. `%25` should not be decoded since it may be part
+    // of an encoded parameter.
+    decodeFragment: function(fragment) {
+      return decodeURI(fragment.replace(/%25/g, '%2525'));
+    },
+
+    // In IE6, the hash fragment and search params are incorrect if the
+    // fragment contains `?`.
+    getSearch: function() {
+      var match = this.location.href.replace(/#.*/, '').match(/\?.+/);
+      return match ? match[0] : '';
+    },
+
+    // Gets the true hash value. Cannot use location.hash directly due to bug
+    // in Firefox where location.hash will always be decoded.
+    getHash: function(window) {
+      var match = (window || this).location.href.match(/#(.*)$/);
+      return match ? match[1] : '';
+    },
+
+    // Get the pathname and search params, without the root.
+    getPath: function() {
+      var path = this.decodeFragment(
+        this.location.pathname + this.getSearch()
+      ).slice(this.root.length - 1);
+      return path.charAt(0) === '/' ? path.slice(1) : path;
+    },
+
+    // Get the cross-browser normalized URL fragment from the path or hash.
+    getFragment: function(fragment) {
+      if (fragment == null) {
+        if (this._usePushState || !this._wantsHashChange) {
+          fragment = this.getPath();
+        } else {
+          fragment = this.getHash();
+        }
+      }
+      return fragment.replace(routeStripper, '');
+    },
+
+    // Start the hash change handling, returning `true` if the current URL matches
+    // an existing route, and `false` otherwise.
+    start: function(options) {
+      if (History.started) throw new Error('Backbone.history has already been started');
+      History.started = true;
+
+      // Figure out the initial configuration. Do we need an iframe?
+      // Is pushState desired ... is it available?
+      this.options          = _.extend({root: '/'}, this.options, options);
+      this.root             = this.options.root;
+      this._wantsHashChange = this.options.hashChange !== false;
+      this._hasHashChange   = 'onhashchange' in window && (document.documentMode === void 0 || document.documentMode > 7);
+      this._useHashChange   = this._wantsHashChange && this._hasHashChange;
+      this._wantsPushState  = !!this.options.pushState;
+      this._hasPushState    = !!(this.history && this.history.pushState);
+      this._usePushState    = this._wantsPushState && this._hasPushState;
+      this.fragment         = this.getFragment();
+
+      // Normalize root to always include a leading and trailing slash.
+      this.root = ('/' + this.root + '/').replace(rootStripper, '/');
+
+      // Transition from hashChange to pushState or vice versa if both are
+      // requested.
+      if (this._wantsHashChange && this._wantsPushState) {
+
+        // If we've started off with a route from a `pushState`-enabled
+        // browser, but we're currently in a browser that doesn't support it...
+        if (!this._hasPushState && !this.atRoot()) {
+          var rootPath = this.root.slice(0, -1) || '/';
+          this.location.replace(rootPath + '#' + this.getPath());
+          // Return immediately as browser will do redirect to new url
+          return true;
+
+        // Or if we've started out with a hash-based route, but we're currently
+        // in a browser where it could be `pushState`-based instead...
+        } else if (this._hasPushState && this.atRoot()) {
+          this.navigate(this.getHash(), {replace: true});
+        }
+
+      }
+
+      // Proxy an iframe to handle location events if the browser doesn't
+      // support the `hashchange` event, HTML5 history, or the user wants
+      // `hashChange` but not `pushState`.
+      if (!this._hasHashChange && this._wantsHashChange && !this._usePushState) {
+        this.iframe = document.createElement('iframe');
+        this.iframe.src = 'javascript:0';
+        this.iframe.style.display = 'none';
+        this.iframe.tabIndex = -1;
+        var body = document.body;
+        // Using `appendChild` will throw on IE < 9 if the document is not ready.
+        var iWindow = body.insertBefore(this.iframe, body.firstChild).contentWindow;
+        iWindow.document.open();
+        iWindow.document.close();
+        iWindow.location.hash = '#' + this.fragment;
+      }
+
+      // Add a cross-platform `addEventListener` shim for older browsers.
+      var addEventListener = window.addEventListener || function(eventName, listener) {
+        return attachEvent('on' + eventName, listener);
+      };
+
+      // Depending on whether we're using pushState or hashes, and whether
+      // 'onhashchange' is supported, determine how we check the URL state.
+      if (this._usePushState) {
+        addEventListener('popstate', this.checkUrl, false);
+      } else if (this._useHashChange && !this.iframe) {
+        addEventListener('hashchange', this.checkUrl, false);
+      } else if (this._wantsHashChange) {
+        this._checkUrlInterval = setInterval(this.checkUrl, this.interval);
+      }
+
+      if (!this.options.silent) return this.loadUrl();
+    },
+
+    // Disable Backbone.history, perhaps temporarily. Not useful in a real app,
+    // but possibly useful for unit testing Routers.
+    stop: function() {
+      // Add a cross-platform `removeEventListener` shim for older browsers.
+      var removeEventListener = window.removeEventListener || function(eventName, listener) {
+        return detachEvent('on' + eventName, listener);
+      };
+
+      // Remove window listeners.
+      if (this._usePushState) {
+        removeEventListener('popstate', this.checkUrl, false);
+      } else if (this._useHashChange && !this.iframe) {
+        removeEventListener('hashchange', this.checkUrl, false);
+      }
+
+      // Clean up the iframe if necessary.
+      if (this.iframe) {
+        document.body.removeChild(this.iframe);
+        this.iframe = null;
+      }
+
+      // Some environments will throw when clearing an undefined interval.
+      if (this._checkUrlInterval) clearInterval(this._checkUrlInterval);
+      History.started = false;
+    },
+
+    // Add a route to be tested when the fragment changes. Routes added later
+    // may override previous routes.
+    route: function(route, callback) {
+      this.handlers.unshift({route: route, callback: callback});
+    },
+
+    // Checks the current URL to see if it has changed, and if it has,
+    // calls `loadUrl`, normalizing across the hidden iframe.
+    checkUrl: function(e) {
+      var current = this.getFragment();
+
+      // If the user pressed the back button, the iframe's hash will have
+      // changed and we should use that for comparison.
+      if (current === this.fragment && this.iframe) {
+        current = this.getHash(this.iframe.contentWindow);
+      }
+
+      if (current === this.fragment) return false;
+      if (this.iframe) this.navigate(current);
+      this.loadUrl();
+    },
+
+    // Attempt to load the current URL fragment. If a route succeeds with a
+    // match, returns `true`. If no defined routes matches the fragment,
+    // returns `false`.
+    loadUrl: function(fragment) {
+      // If the root doesn't match, no routes can match either.
+      if (!this.matchRoot()) return false;
+      fragment = this.fragment = this.getFragment(fragment);
+      return _.some(this.handlers, function(handler) {
+        if (handler.route.test(fragment)) {
+          handler.callback(fragment);
+          return true;
+        }
+      });
+    },
+
+    // Save a fragment into the hash history, or replace the URL state if the
+    // 'replace' option is passed. You are responsible for properly URL-encoding
+    // the fragment in advance.
+    //
+    // The options object can contain `trigger: true` if you wish to have the
+    // route callback be fired (not usually desirable), or `replace: true`, if
+    // you wish to modify the current URL without adding an entry to the history.
+    navigate: function(fragment, options) {
+      if (!History.started) return false;
+      if (!options || options === true) options = {trigger: !!options};
+
+      // Normalize the fragment.
+      fragment = this.getFragment(fragment || '');
+
+      // Don't include a trailing slash on the root.
+      var rootPath = this.root;
+      if (fragment === '' || fragment.charAt(0) === '?') {
+        rootPath = rootPath.slice(0, -1) || '/';
+      }
+      var url = rootPath + fragment;
+
+      // Strip the hash and decode for matching.
+      fragment = this.decodeFragment(fragment.replace(pathStripper, ''));
+
+      if (this.fragment === fragment) return;
+      this.fragment = fragment;
+
+      // If pushState is available, we use it to set the fragment as a real URL.
+      if (this._usePushState) {
+        this.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
+
+      // If hash changes haven't been explicitly disabled, update the hash
+      // fragment to store history.
+      } else if (this._wantsHashChange) {
+        this._updateHash(this.location, fragment, options.replace);
+        if (this.iframe && fragment !== this.getHash(this.iframe.contentWindow)) {
+          var iWindow = this.iframe.contentWindow;
+
+          // Opening and closing the iframe tricks IE7 and earlier to push a
+          // history entry on hash-tag change.  When replace is true, we don't
+          // want this.
+          if (!options.replace) {
+            iWindow.document.open();
+            iWindow.document.close();
+          }
+
+          this._updateHash(iWindow.location, fragment, options.replace);
+        }
+
+      // If you've told us that you explicitly don't want fallback hashchange-
+      // based history, then `navigate` becomes a page refresh.
+      } else {
+        return this.location.assign(url);
+      }
+      if (options.trigger) return this.loadUrl(fragment);
+    },
+
+    // Update the hash location, either replacing the current entry, or adding
+    // a new one to the browser history.
+    _updateHash: function(location, fragment, replace) {
+      if (replace) {
+        var href = location.href.replace(/(javascript:|#).*$/, '');
+        location.replace(href + '#' + fragment);
+      } else {
+        // Some browsers require that `hash` contains a leading #.
+        location.hash = '#' + fragment;
+      }
+    }
+
+  });
+
+  // Create the default Backbone.history.
+  Backbone.history = new History;
+
+  // Helpers
+  // -------
+
+  // Helper function to correctly set up the prototype chain for subclasses.
+  // Similar to `goog.inherits`, but uses a hash of prototype properties and
+  // class properties to be extended.
+  var extend = function(protoProps, staticProps) {
+    var parent = this;
+    var child;
+
+    // The constructor function for the new subclass is either defined by you
+    // (the "constructor" property in your `extend` definition), or defaulted
+    // by us to simply call the parent constructor.
+    if (protoProps && _.has(protoProps, 'constructor')) {
+      child = protoProps.constructor;
+    } else {
+      child = function(){ return parent.apply(this, arguments); };
+    }
+
+    // Add static properties to the constructor function, if supplied.
+    _.extend(child, parent, staticProps);
+
+    // Set the prototype chain to inherit from `parent`, without calling
+    // `parent`'s constructor function and add the prototype properties.
+    child.prototype = _.create(parent.prototype, protoProps);
+    child.prototype.constructor = child;
+
+    // Set a convenience property in case the parent's prototype is needed
+    // later.
+    child.__super__ = parent.prototype;
+
+    return child;
+  };
+
+  // Set up inheritance for the model, collection, router, view and history.
+  Model.extend = Collection.extend = Router.extend = View.extend = History.extend = extend;
+
+  // Throw an error when a URL is needed, and none is supplied.
+  var urlError = function() {
+    throw new Error('A "url" property or function must be specified');
+  };
+
+  // Wrap an optional error callback with a fallback error event.
+  var wrapError = function(model, options) {
+    var error = options.error;
+    options.error = function(resp) {
+      if (error) error.call(options.context, model, resp, options);
+      model.trigger('error', model, resp, options);
+    };
+  };
+
+  return Backbone;
+});
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/collection.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/collection.js
new file mode 100644
index 0000000..dd98aca
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/collection.js
@@ -0,0 +1,1998 @@
+(function() {
+
+  var a, b, c, d, e, col, otherCol;
+
+  QUnit.module('Backbone.Collection', {
+
+    beforeEach: function(assert) {
+      a         = new Backbone.Model({id: 3, label: 'a'});
+      b         = new Backbone.Model({id: 2, label: 'b'});
+      c         = new Backbone.Model({id: 1, label: 'c'});
+      d         = new Backbone.Model({id: 0, label: 'd'});
+      e         = null;
+      col       = new Backbone.Collection([a, b, c, d]);
+      otherCol  = new Backbone.Collection();
+    }
+
+  });
+
+  QUnit.test('new and sort', function(assert) {
+    assert.expect(6);
+    var counter = 0;
+    col.on('sort', function(){ counter++; });
+    assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
+    col.comparator = function(m1, m2) {
+      return m1.id > m2.id ? -1 : 1;
+    };
+    col.sort();
+    assert.equal(counter, 1);
+    assert.deepEqual(col.pluck('label'), ['a', 'b', 'c', 'd']);
+    col.comparator = function(model) { return model.id; };
+    col.sort();
+    assert.equal(counter, 2);
+    assert.deepEqual(col.pluck('label'), ['d', 'c', 'b', 'a']);
+    assert.equal(col.length, 4);
+  });
+
+  QUnit.test('String comparator.', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([
+      {id: 3},
+      {id: 1},
+      {id: 2}
+    ], {comparator: 'id'});
+    assert.deepEqual(collection.pluck('id'), [1, 2, 3]);
+  });
+
+  QUnit.test('new and parse', function(assert) {
+    assert.expect(3);
+    var Collection = Backbone.Collection.extend({
+      parse: function(data) {
+        return _.filter(data, function(datum) {
+          return datum.a % 2 === 0;
+        });
+      }
+    });
+    var models = [{a: 1}, {a: 2}, {a: 3}, {a: 4}];
+    var collection = new Collection(models, {parse: true});
+    assert.strictEqual(collection.length, 2);
+    assert.strictEqual(collection.first().get('a'), 2);
+    assert.strictEqual(collection.last().get('a'), 4);
+  });
+
+  QUnit.test('clone preserves model and comparator', function(assert) {
+    assert.expect(3);
+    var Model = Backbone.Model.extend();
+    var comparator = function(model){ return model.id; };
+
+    var collection = new Backbone.Collection([{id: 1}], {
+      model: Model,
+      comparator: comparator
+    }).clone();
+    collection.add({id: 2});
+    assert.ok(collection.at(0) instanceof Model);
+    assert.ok(collection.at(1) instanceof Model);
+    assert.strictEqual(collection.comparator, comparator);
+  });
+
+  QUnit.test('get', function(assert) {
+    assert.expect(6);
+    assert.equal(col.get(0), d);
+    assert.equal(col.get(d.clone()), d);
+    assert.equal(col.get(2), b);
+    assert.equal(col.get({id: 1}), c);
+    assert.equal(col.get(c.clone()), c);
+    assert.equal(col.get(col.first().cid), col.first());
+  });
+
+  QUnit.test('get with non-default ids', function(assert) {
+    assert.expect(5);
+    var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
+    var model = new MongoModel({_id: 100});
+    var collection = new Backbone.Collection([model], {model: MongoModel});
+    assert.equal(collection.get(100), model);
+    assert.equal(collection.get(model.cid), model);
+    assert.equal(collection.get(model), model);
+    assert.equal(collection.get(101), void 0);
+
+    var collection2 = new Backbone.Collection();
+    collection2.model = MongoModel;
+    collection2.add(model.attributes);
+    assert.equal(collection2.get(model.clone()), collection2.first());
+  });
+
+  QUnit.test('has', function(assert) {
+    assert.expect(15);
+    assert.ok(col.has(a));
+    assert.ok(col.has(b));
+    assert.ok(col.has(c));
+    assert.ok(col.has(d));
+    assert.ok(col.has(a.id));
+    assert.ok(col.has(b.id));
+    assert.ok(col.has(c.id));
+    assert.ok(col.has(d.id));
+    assert.ok(col.has(a.cid));
+    assert.ok(col.has(b.cid));
+    assert.ok(col.has(c.cid));
+    assert.ok(col.has(d.cid));
+    var outsider = new Backbone.Model({id: 4});
+    assert.notOk(col.has(outsider));
+    assert.notOk(col.has(outsider.id));
+    assert.notOk(col.has(outsider.cid));
+  });
+
+  QUnit.test('update index when id changes', function(assert) {
+    assert.expect(4);
+    var collection = new Backbone.Collection();
+    collection.add([
+      {id: 0, name: 'one'},
+      {id: 1, name: 'two'}
+    ]);
+    var one = collection.get(0);
+    assert.equal(one.get('name'), 'one');
+    collection.on('change:name', function(model) { assert.ok(this.get(model)); });
+    one.set({name: 'dalmatians', id: 101});
+    assert.equal(collection.get(0), null);
+    assert.equal(collection.get(101).get('name'), 'dalmatians');
+  });
+
+  QUnit.test('at', function(assert) {
+    assert.expect(2);
+    assert.equal(col.at(2), c);
+    assert.equal(col.at(-2), c);
+  });
+
+  QUnit.test('pluck', function(assert) {
+    assert.expect(1);
+    assert.equal(col.pluck('label').join(' '), 'a b c d');
+  });
+
+  QUnit.test('add', function(assert) {
+    assert.expect(14);
+    var added, opts, secondAdded;
+    added = opts = secondAdded = null;
+    e = new Backbone.Model({id: 10, label: 'e'});
+    otherCol.add(e);
+    otherCol.on('add', function() {
+      secondAdded = true;
+    });
+    col.on('add', function(model, collection, options){
+      added = model.get('label');
+      opts = options;
+    });
+    col.add(e, {amazing: true});
+    assert.equal(added, 'e');
+    assert.equal(col.length, 5);
+    assert.equal(col.last(), e);
+    assert.equal(otherCol.length, 1);
+    assert.equal(secondAdded, null);
+    assert.ok(opts.amazing);
+
+    var f = new Backbone.Model({id: 20, label: 'f'});
+    var g = new Backbone.Model({id: 21, label: 'g'});
+    var h = new Backbone.Model({id: 22, label: 'h'});
+    var atCol = new Backbone.Collection([f, g, h]);
+    assert.equal(atCol.length, 3);
+    atCol.add(e, {at: 1});
+    assert.equal(atCol.length, 4);
+    assert.equal(atCol.at(1), e);
+    assert.equal(atCol.last(), h);
+
+    var coll = new Backbone.Collection(new Array(2));
+    var addCount = 0;
+    coll.on('add', function(){
+      addCount += 1;
+    });
+    coll.add([undefined, f, g]);
+    assert.equal(coll.length, 5);
+    assert.equal(addCount, 3);
+    coll.add(new Array(4));
+    assert.equal(coll.length, 9);
+    assert.equal(addCount, 7);
+  });
+
+  QUnit.test('add multiple models', function(assert) {
+    assert.expect(6);
+    var collection = new Backbone.Collection([{at: 0}, {at: 1}, {at: 9}]);
+    collection.add([{at: 2}, {at: 3}, {at: 4}, {at: 5}, {at: 6}, {at: 7}, {at: 8}], {at: 2});
+    for (var i = 0; i <= 5; i++) {
+      assert.equal(collection.at(i).get('at'), i);
+    }
+  });
+
+  QUnit.test('add; at should have preference over comparator', function(assert) {
+    assert.expect(1);
+    var Col = Backbone.Collection.extend({
+      comparator: function(m1, m2) {
+        return m1.id > m2.id ? -1 : 1;
+      }
+    });
+
+    var collection = new Col([{id: 2}, {id: 3}]);
+    collection.add(new Backbone.Model({id: 1}), {at: 1});
+
+    assert.equal(collection.pluck('id').join(' '), '3 1 2');
+  });
+
+  QUnit.test('add; at should add to the end if the index is out of bounds', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 2}, {id: 3}]);
+    collection.add(new Backbone.Model({id: 1}), {at: 5});
+
+    assert.equal(collection.pluck('id').join(' '), '2 3 1');
+  });
+
+  QUnit.test("can't add model to collection twice", function(assert) {
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 1}, {id: 2}, {id: 3}]);
+    assert.equal(collection.pluck('id').join(' '), '1 2 3');
+  });
+
+  QUnit.test("can't add different model with same id to collection twice", function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection;
+    collection.unshift({id: 101});
+    collection.add({id: 101});
+    assert.equal(collection.length, 1);
+  });
+
+  QUnit.test('merge in duplicate models with {merge: true}', function(assert) {
+    assert.expect(3);
+    var collection = new Backbone.Collection;
+    collection.add([{id: 1, name: 'Moe'}, {id: 2, name: 'Curly'}, {id: 3, name: 'Larry'}]);
+    collection.add({id: 1, name: 'Moses'});
+    assert.equal(collection.first().get('name'), 'Moe');
+    collection.add({id: 1, name: 'Moses'}, {merge: true});
+    assert.equal(collection.first().get('name'), 'Moses');
+    collection.add({id: 1, name: 'Tim'}, {merge: true, silent: true});
+    assert.equal(collection.first().get('name'), 'Tim');
+  });
+
+  QUnit.test('add model to multiple collections', function(assert) {
+    assert.expect(10);
+    var counter = 0;
+    var m = new Backbone.Model({id: 10, label: 'm'});
+    m.on('add', function(model, collection) {
+      counter++;
+      assert.equal(m, model);
+      if (counter > 1) {
+        assert.equal(collection, col2);
+      } else {
+        assert.equal(collection, col1);
+      }
+    });
+    var col1 = new Backbone.Collection([]);
+    col1.on('add', function(model, collection) {
+      assert.equal(m, model);
+      assert.equal(col1, collection);
+    });
+    var col2 = new Backbone.Collection([]);
+    col2.on('add', function(model, collection) {
+      assert.equal(m, model);
+      assert.equal(col2, collection);
+    });
+    col1.add(m);
+    assert.equal(m.collection, col1);
+    col2.add(m);
+    assert.equal(m.collection, col1);
+  });
+
+  QUnit.test('add model with parse', function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      parse: function(obj) {
+        obj.value += 1;
+        return obj;
+      }
+    });
+
+    var Col = Backbone.Collection.extend({model: Model});
+    var collection = new Col;
+    collection.add({value: 1}, {parse: true});
+    assert.equal(collection.at(0).get('value'), 2);
+  });
+
+  QUnit.test('add with parse and merge', function(assert) {
+    var collection = new Backbone.Collection();
+    collection.parse = function(attrs) {
+      return _.map(attrs, function(model) {
+        if (model.model) return model.model;
+        return model;
+      });
+    };
+    collection.add({id: 1});
+    collection.add({model: {id: 1, name: 'Alf'}}, {parse: true, merge: true});
+    assert.equal(collection.first().get('name'), 'Alf');
+  });
+
+  QUnit.test('add model to collection with sort()-style comparator', function(assert) {
+    assert.expect(3);
+    var collection = new Backbone.Collection;
+    collection.comparator = function(m1, m2) {
+      return m1.get('name') < m2.get('name') ? -1 : 1;
+    };
+    var tom = new Backbone.Model({name: 'Tom'});
+    var rob = new Backbone.Model({name: 'Rob'});
+    var tim = new Backbone.Model({name: 'Tim'});
+    collection.add(tom);
+    collection.add(rob);
+    collection.add(tim);
+    assert.equal(collection.indexOf(rob), 0);
+    assert.equal(collection.indexOf(tim), 1);
+    assert.equal(collection.indexOf(tom), 2);
+  });
+
+  QUnit.test('comparator that depends on `this`', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection;
+    collection.negative = function(num) {
+      return -num;
+    };
+    collection.comparator = function(model) {
+      return this.negative(model.id);
+    };
+    collection.add([{id: 1}, {id: 2}, {id: 3}]);
+    assert.deepEqual(collection.pluck('id'), [3, 2, 1]);
+    collection.comparator = function(m1, m2) {
+      return this.negative(m2.id) - this.negative(m1.id);
+    };
+    collection.sort();
+    assert.deepEqual(collection.pluck('id'), [1, 2, 3]);
+  });
+
+  QUnit.test('remove', function(assert) {
+    assert.expect(12);
+    var removed = null;
+    var result = null;
+    col.on('remove', function(model, collection, options) {
+      removed = model.get('label');
+      assert.equal(options.index, 3);
+      assert.equal(collection.get(model), undefined, '#3693: model cannot be fetched from collection');
+    });
+    result = col.remove(d);
+    assert.equal(removed, 'd');
+    assert.strictEqual(result, d);
+    //if we try to remove d again, it's not going to actually get removed
+    result = col.remove(d);
+    assert.strictEqual(result, undefined);
+    assert.equal(col.length, 3);
+    assert.equal(col.first(), a);
+    col.off();
+    result = col.remove([c, d]);
+    assert.equal(result.length, 1, 'only returns removed models');
+    assert.equal(result[0], c, 'only returns removed models');
+    result = col.remove([c, b]);
+    assert.equal(result.length, 1, 'only returns removed models');
+    assert.equal(result[0], b, 'only returns removed models');
+    result = col.remove([]);
+    assert.deepEqual(result, [], 'returns empty array when nothing removed');
+  });
+
+  QUnit.test('add and remove return values', function(assert) {
+    assert.expect(13);
+    var Even = Backbone.Model.extend({
+      validate: function(attrs) {
+        if (attrs.id % 2 !== 0) return 'odd';
+      }
+    });
+    var collection = new Backbone.Collection;
+    collection.model = Even;
+
+    var list = collection.add([{id: 2}, {id: 4}], {validate: true});
+    assert.equal(list.length, 2);
+    assert.ok(list[0] instanceof Backbone.Model);
+    assert.equal(list[1], collection.last());
+    assert.equal(list[1].get('id'), 4);
+
+    list = collection.add([{id: 3}, {id: 6}], {validate: true});
+    assert.equal(collection.length, 3);
+    assert.equal(list[0], false);
+    assert.equal(list[1].get('id'), 6);
+
+    var result = collection.add({id: 6});
+    assert.equal(result.cid, list[1].cid);
+
+    result = collection.remove({id: 6});
+    assert.equal(collection.length, 2);
+    assert.equal(result.id, 6);
+
+    list = collection.remove([{id: 2}, {id: 8}]);
+    assert.equal(collection.length, 1);
+    assert.equal(list[0].get('id'), 2);
+    assert.equal(list[1], null);
+  });
+
+  QUnit.test('shift and pop', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
+    assert.equal(collection.shift().get('a'), 'a');
+    assert.equal(collection.pop().get('c'), 'c');
+  });
+
+  QUnit.test('slice', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection([{a: 'a'}, {b: 'b'}, {c: 'c'}]);
+    var array = collection.slice(1, 3);
+    assert.equal(array.length, 2);
+    assert.equal(array[0].get('b'), 'b');
+  });
+
+  QUnit.test('events are unbound on remove', function(assert) {
+    assert.expect(3);
+    var counter = 0;
+    var dj = new Backbone.Model();
+    var emcees = new Backbone.Collection([dj]);
+    emcees.on('change', function(){ counter++; });
+    dj.set({name: 'Kool'});
+    assert.equal(counter, 1);
+    emcees.reset([]);
+    assert.equal(dj.collection, undefined);
+    dj.set({name: 'Shadow'});
+    assert.equal(counter, 1);
+  });
+
+  QUnit.test('remove in multiple collections', function(assert) {
+    assert.expect(7);
+    var modelData = {
+      id: 5,
+      title: 'Othello'
+    };
+    var passed = false;
+    var m1 = new Backbone.Model(modelData);
+    var m2 = new Backbone.Model(modelData);
+    m2.on('remove', function() {
+      passed = true;
+    });
+    var col1 = new Backbone.Collection([m1]);
+    var col2 = new Backbone.Collection([m2]);
+    assert.notEqual(m1, m2);
+    assert.ok(col1.length === 1);
+    assert.ok(col2.length === 1);
+    col1.remove(m1);
+    assert.equal(passed, false);
+    assert.ok(col1.length === 0);
+    col2.remove(m1);
+    assert.ok(col2.length === 0);
+    assert.equal(passed, true);
+  });
+
+  QUnit.test('remove same model in multiple collection', function(assert) {
+    assert.expect(16);
+    var counter = 0;
+    var m = new Backbone.Model({id: 5, title: 'Othello'});
+    m.on('remove', function(model, collection) {
+      counter++;
+      assert.equal(m, model);
+      if (counter > 1) {
+        assert.equal(collection, col1);
+      } else {
+        assert.equal(collection, col2);
+      }
+    });
+    var col1 = new Backbone.Collection([m]);
+    col1.on('remove', function(model, collection) {
+      assert.equal(m, model);
+      assert.equal(col1, collection);
+    });
+    var col2 = new Backbone.Collection([m]);
+    col2.on('remove', function(model, collection) {
+      assert.equal(m, model);
+      assert.equal(col2, collection);
+    });
+    assert.equal(col1, m.collection);
+    col2.remove(m);
+    assert.ok(col2.length === 0);
+    assert.ok(col1.length === 1);
+    assert.equal(counter, 1);
+    assert.equal(col1, m.collection);
+    col1.remove(m);
+    assert.equal(null, m.collection);
+    assert.ok(col1.length === 0);
+    assert.equal(counter, 2);
+  });
+
+  QUnit.test('model destroy removes from all collections', function(assert) {
+    assert.expect(3);
+    var m = new Backbone.Model({id: 5, title: 'Othello'});
+    m.sync = function(method, model, options) { options.success(); };
+    var col1 = new Backbone.Collection([m]);
+    var col2 = new Backbone.Collection([m]);
+    m.destroy();
+    assert.ok(col1.length === 0);
+    assert.ok(col2.length === 0);
+    assert.equal(undefined, m.collection);
+  });
+
+  QUnit.test('Collection: non-persisted model destroy removes from all collections', function(assert) {
+    assert.expect(3);
+    var m = new Backbone.Model({title: 'Othello'});
+    m.sync = function(method, model, options) { throw 'should not be called'; };
+    var col1 = new Backbone.Collection([m]);
+    var col2 = new Backbone.Collection([m]);
+    m.destroy();
+    assert.ok(col1.length === 0);
+    assert.ok(col2.length === 0);
+    assert.equal(undefined, m.collection);
+  });
+
+  QUnit.test('fetch', function(assert) {
+    assert.expect(4);
+    var collection = new Backbone.Collection;
+    collection.url = '/test';
+    collection.fetch();
+    assert.equal(this.syncArgs.method, 'read');
+    assert.equal(this.syncArgs.model, collection);
+    assert.equal(this.syncArgs.options.parse, true);
+
+    collection.fetch({parse: false});
+    assert.equal(this.syncArgs.options.parse, false);
+  });
+
+  QUnit.test('fetch with an error response triggers an error event', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection();
+    collection.on('error', function() {
+      assert.ok(true);
+    });
+    collection.sync = function(method, model, options) { options.error(); };
+    collection.fetch();
+  });
+
+  QUnit.test('#3283 - fetch with an error response calls error with context', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection();
+    var obj = {};
+    var options = {
+      context: obj,
+      error: function() {
+        assert.equal(this, obj);
+      }
+    };
+    collection.sync = function(method, model, opts) {
+      opts.error.call(opts.context);
+    };
+    collection.fetch(options);
+  });
+
+  QUnit.test('ensure fetch only parses once', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection;
+    var counter = 0;
+    collection.parse = function(models) {
+      counter++;
+      return models;
+    };
+    collection.url = '/test';
+    collection.fetch();
+    this.syncArgs.options.success([]);
+    assert.equal(counter, 1);
+  });
+
+  QUnit.test('create', function(assert) {
+    assert.expect(4);
+    var collection = new Backbone.Collection;
+    collection.url = '/test';
+    var model = collection.create({label: 'f'}, {wait: true});
+    assert.equal(this.syncArgs.method, 'create');
+    assert.equal(this.syncArgs.model, model);
+    assert.equal(model.get('label'), 'f');
+    assert.equal(model.collection, collection);
+  });
+
+  QUnit.test('create with validate:true enforces validation', function(assert) {
+    assert.expect(3);
+    var ValidatingModel = Backbone.Model.extend({
+      validate: function(attrs) {
+        return 'fail';
+      }
+    });
+    var ValidatingCollection = Backbone.Collection.extend({
+      model: ValidatingModel
+    });
+    var collection = new ValidatingCollection();
+    collection.on('invalid', function(coll, error, options) {
+      assert.equal(error, 'fail');
+      assert.equal(options.validationError, 'fail');
+    });
+    assert.equal(collection.create({'foo': 'bar'}, {validate: true}), false);
+  });
+
+  QUnit.test('create will pass extra options to success callback', function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      sync: function(method, model, options) {
+        _.extend(options, {specialSync: true});
+        return Backbone.Model.prototype.sync.call(this, method, model, options);
+      }
+    });
+
+    var Collection = Backbone.Collection.extend({
+      model: Model,
+      url: '/test'
+    });
+
+    var collection = new Collection;
+
+    var success = function(model, response, options) {
+      assert.ok(options.specialSync, 'Options were passed correctly to callback');
+    };
+
+    collection.create({}, {success: success});
+    this.ajaxSettings.success();
+  });
+
+  QUnit.test('create with wait:true should not call collection.parse', function(assert) {
+    assert.expect(0);
+    var Collection = Backbone.Collection.extend({
+      url: '/test',
+      parse: function() {
+        assert.ok(false);
+      }
+    });
+
+    var collection = new Collection;
+
+    collection.create({}, {wait: true});
+    this.ajaxSettings.success();
+  });
+
+  QUnit.test('a failing create returns model with errors', function(assert) {
+    var ValidatingModel = Backbone.Model.extend({
+      validate: function(attrs) {
+        return 'fail';
+      }
+    });
+    var ValidatingCollection = Backbone.Collection.extend({
+      model: ValidatingModel
+    });
+    var collection = new ValidatingCollection();
+    var m = collection.create({foo: 'bar'});
+    assert.equal(m.validationError, 'fail');
+    assert.equal(collection.length, 1);
+  });
+
+  QUnit.test('initialize', function(assert) {
+    assert.expect(1);
+    var Collection = Backbone.Collection.extend({
+      initialize: function() {
+        this.one = 1;
+      }
+    });
+    var coll = new Collection;
+    assert.equal(coll.one, 1);
+  });
+
+  QUnit.test('toJSON', function(assert) {
+    assert.expect(1);
+    assert.equal(JSON.stringify(col), '[{"id":3,"label":"a"},{"id":2,"label":"b"},{"id":1,"label":"c"},{"id":0,"label":"d"}]');
+  });
+
+  QUnit.test('where and findWhere', function(assert) {
+    assert.expect(8);
+    var model = new Backbone.Model({a: 1});
+    var coll = new Backbone.Collection([
+      model,
+      {a: 1},
+      {a: 1, b: 2},
+      {a: 2, b: 2},
+      {a: 3}
+    ]);
+    assert.equal(coll.where({a: 1}).length, 3);
+    assert.equal(coll.where({a: 2}).length, 1);
+    assert.equal(coll.where({a: 3}).length, 1);
+    assert.equal(coll.where({b: 1}).length, 0);
+    assert.equal(coll.where({b: 2}).length, 2);
+    assert.equal(coll.where({a: 1, b: 2}).length, 1);
+    assert.equal(coll.findWhere({a: 1}), model);
+    assert.equal(coll.findWhere({a: 4}), void 0);
+  });
+
+  QUnit.test('Underscore methods', function(assert) {
+    assert.expect(21);
+    assert.equal(col.map(function(model){ return model.get('label'); }).join(' '), 'a b c d');
+    assert.equal(col.some(function(model){ return model.id === 100; }), false);
+    assert.equal(col.some(function(model){ return model.id === 0; }), true);
+    assert.equal(col.reduce(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3);
+    assert.equal(col.reduceRight(function(m1, m2) {return m1.id > m2.id ? m1 : m2;}).id, 3);
+    assert.equal(col.indexOf(b), 1);
+    assert.equal(col.size(), 4);
+    assert.equal(col.rest().length, 3);
+    assert.ok(!_.includes(col.rest(), a));
+    assert.ok(_.includes(col.rest(), d));
+    assert.ok(!col.isEmpty());
+    assert.ok(!_.includes(col.without(d), d));
+
+    var wrapped = col.chain();
+    assert.equal(wrapped.map('id').max().value(), 3);
+    assert.equal(wrapped.map('id').min().value(), 0);
+    assert.deepEqual(wrapped
+      .filter(function(o){ return o.id % 2 === 0; })
+      .map(function(o){ return o.id * 2; })
+      .value(),
+      [4, 0]);
+    assert.deepEqual(col.difference([c, d]), [a, b]);
+    assert.ok(col.includes(col.sample()));
+
+    var first = col.first();
+    assert.deepEqual(col.groupBy(function(model){ return model.id; })[first.id], [first]);
+    assert.deepEqual(col.countBy(function(model){ return model.id; }), {0: 1, 1: 1, 2: 1, 3: 1});
+    assert.deepEqual(col.sortBy(function(model){ return model.id; })[0], col.at(3));
+    assert.ok(col.indexBy('id')[first.id] === first);
+  });
+
+  QUnit.test('Underscore methods with object-style and property-style iteratee', function(assert) {
+    assert.expect(26);
+    var model = new Backbone.Model({a: 4, b: 1, e: 3});
+    var coll = new Backbone.Collection([
+      {a: 1, b: 1},
+      {a: 2, b: 1, c: 1},
+      {a: 3, b: 1},
+      model
+    ]);
+    assert.equal(coll.find({a: 0}), undefined);
+    assert.deepEqual(coll.find({a: 4}), model);
+    assert.equal(coll.find('d'), undefined);
+    assert.deepEqual(coll.find('e'), model);
+    assert.equal(coll.filter({a: 0}), false);
+    assert.deepEqual(coll.filter({a: 4}), [model]);
+    assert.equal(coll.some({a: 0}), false);
+    assert.equal(coll.some({a: 1}), true);
+    assert.equal(coll.reject({a: 0}).length, 4);
+    assert.deepEqual(coll.reject({a: 4}), _.without(coll.models, model));
+    assert.equal(coll.every({a: 0}), false);
+    assert.equal(coll.every({b: 1}), true);
+    assert.deepEqual(coll.partition({a: 0})[0], []);
+    assert.deepEqual(coll.partition({a: 0})[1], coll.models);
+    assert.deepEqual(coll.partition({a: 4})[0], [model]);
+    assert.deepEqual(coll.partition({a: 4})[1], _.without(coll.models, model));
+    assert.deepEqual(coll.map({a: 2}), [false, true, false, false]);
+    assert.deepEqual(coll.map('a'), [1, 2, 3, 4]);
+    assert.deepEqual(coll.sortBy('a')[3], model);
+    assert.deepEqual(coll.sortBy('e')[0], model);
+    assert.deepEqual(coll.countBy({a: 4}), {'false': 3, 'true': 1});
+    assert.deepEqual(coll.countBy('d'), {'undefined': 4});
+    assert.equal(coll.findIndex({b: 1}), 0);
+    assert.equal(coll.findIndex({b: 9}), -1);
+    assert.equal(coll.findLastIndex({b: 1}), 3);
+    assert.equal(coll.findLastIndex({b: 9}), -1);
+  });
+
+  QUnit.test('reset', function(assert) {
+    assert.expect(16);
+
+    var resetCount = 0;
+    var models = col.models;
+    col.on('reset', function() { resetCount += 1; });
+    col.reset([]);
+    assert.equal(resetCount, 1);
+    assert.equal(col.length, 0);
+    assert.equal(col.last(), null);
+    col.reset(models);
+    assert.equal(resetCount, 2);
+    assert.equal(col.length, 4);
+    assert.equal(col.last(), d);
+    col.reset(_.map(models, function(m){ return m.attributes; }));
+    assert.equal(resetCount, 3);
+    assert.equal(col.length, 4);
+    assert.ok(col.last() !== d);
+    assert.ok(_.isEqual(col.last().attributes, d.attributes));
+    col.reset();
+    assert.equal(col.length, 0);
+    assert.equal(resetCount, 4);
+
+    var f = new Backbone.Model({id: 20, label: 'f'});
+    col.reset([undefined, f]);
+    assert.equal(col.length, 2);
+    assert.equal(resetCount, 5);
+
+    col.reset(new Array(4));
+    assert.equal(col.length, 4);
+    assert.equal(resetCount, 6);
+  });
+
+  QUnit.test('reset with different values', function(assert) {
+    var collection = new Backbone.Collection({id: 1});
+    collection.reset({id: 1, a: 1});
+    assert.equal(collection.get(1).get('a'), 1);
+  });
+
+  QUnit.test('same references in reset', function(assert) {
+    var model = new Backbone.Model({id: 1});
+    var collection = new Backbone.Collection({id: 1});
+    collection.reset(model);
+    assert.equal(collection.get(1), model);
+  });
+
+  QUnit.test('reset passes caller options', function(assert) {
+    assert.expect(3);
+    var Model = Backbone.Model.extend({
+      initialize: function(attrs, options) {
+        this.modelParameter = options.modelParameter;
+      }
+    });
+    var collection = new (Backbone.Collection.extend({model: Model}))();
+    collection.reset([{astring: 'green', anumber: 1}, {astring: 'blue', anumber: 2}], {modelParameter: 'model parameter'});
+    assert.equal(collection.length, 2);
+    collection.each(function(model) {
+      assert.equal(model.modelParameter, 'model parameter');
+    });
+  });
+
+  QUnit.test('reset does not alter options by reference', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection([{id: 1}]);
+    var origOpts = {};
+    collection.on('reset', function(coll, opts){
+      assert.equal(origOpts.previousModels, undefined);
+      assert.equal(opts.previousModels[0].id, 1);
+    });
+    collection.reset([], origOpts);
+  });
+
+  QUnit.test('trigger custom events on models', function(assert) {
+    assert.expect(1);
+    var fired = null;
+    a.on('custom', function() { fired = true; });
+    a.trigger('custom');
+    assert.equal(fired, true);
+  });
+
+  QUnit.test('add does not alter arguments', function(assert) {
+    assert.expect(2);
+    var attrs = {};
+    var models = [attrs];
+    new Backbone.Collection().add(models);
+    assert.equal(models.length, 1);
+    assert.ok(attrs === models[0]);
+  });
+
+  QUnit.test('#714: access `model.collection` in a brand new model.', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection;
+    collection.url = '/test';
+    var Model = Backbone.Model.extend({
+      set: function(attrs) {
+        assert.equal(attrs.prop, 'value');
+        assert.equal(this.collection, collection);
+        return this;
+      }
+    });
+    collection.model = Model;
+    collection.create({prop: 'value'});
+  });
+
+  QUnit.test('#574, remove its own reference to the .models array.', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection([
+      {id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}
+    ]);
+    assert.equal(collection.length, 6);
+    collection.remove(collection.models);
+    assert.equal(collection.length, 0);
+  });
+
+  QUnit.test('#861, adding models to a collection which do not pass validation, with validate:true', function(assert) {
+    assert.expect(2);
+    var Model = Backbone.Model.extend({
+      validate: function(attrs) {
+        if (attrs.id === 3) return "id can't be 3";
+      }
+    });
+
+    var Collection = Backbone.Collection.extend({
+      model: Model
+    });
+
+    var collection = new Collection;
+    collection.on('invalid', function() { assert.ok(true); });
+
+    collection.add([{id: 1}, {id: 2}, {id: 3}, {id: 4}, {id: 5}, {id: 6}], {validate: true});
+    assert.deepEqual(collection.pluck('id'), [1, 2, 4, 5, 6]);
+  });
+
+  QUnit.test('Invalid models are discarded with validate:true.', function(assert) {
+    assert.expect(5);
+    var collection = new Backbone.Collection;
+    collection.on('test', function() { assert.ok(true); });
+    collection.model = Backbone.Model.extend({
+      validate: function(attrs){ if (!attrs.valid) return 'invalid'; }
+    });
+    var model = new collection.model({id: 1, valid: true});
+    collection.add([model, {id: 2}], {validate: true});
+    model.trigger('test');
+    assert.ok(collection.get(model.cid));
+    assert.ok(collection.get(1));
+    assert.ok(!collection.get(2));
+    assert.equal(collection.length, 1);
+  });
+
+  QUnit.test('multiple copies of the same model', function(assert) {
+    assert.expect(3);
+    var collection = new Backbone.Collection();
+    var model = new Backbone.Model();
+    collection.add([model, model]);
+    assert.equal(collection.length, 1);
+    collection.add([{id: 1}, {id: 1}]);
+    assert.equal(collection.length, 2);
+    assert.equal(collection.last().id, 1);
+  });
+
+  QUnit.test('#964 - collection.get return inconsistent', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection();
+    assert.ok(collection.get(null) === undefined);
+    assert.ok(collection.get() === undefined);
+  });
+
+  QUnit.test('#1112 - passing options.model sets collection.model', function(assert) {
+    assert.expect(2);
+    var Model = Backbone.Model.extend({});
+    var collection = new Backbone.Collection([{id: 1}], {model: Model});
+    assert.ok(collection.model === Model);
+    assert.ok(collection.at(0) instanceof Model);
+  });
+
+  QUnit.test('null and undefined are invalid ids.', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model({id: 1});
+    var collection = new Backbone.Collection([model]);
+    model.set({id: null});
+    assert.ok(!collection.get('null'));
+    model.set({id: 1});
+    model.set({id: undefined});
+    assert.ok(!collection.get('undefined'));
+  });
+
+  QUnit.test('falsy comparator', function(assert) {
+    assert.expect(4);
+    var Col = Backbone.Collection.extend({
+      comparator: function(model){ return model.id; }
+    });
+    var collection = new Col();
+    var colFalse = new Col(null, {comparator: false});
+    var colNull = new Col(null, {comparator: null});
+    var colUndefined = new Col(null, {comparator: undefined});
+    assert.ok(collection.comparator);
+    assert.ok(!colFalse.comparator);
+    assert.ok(!colNull.comparator);
+    assert.ok(colUndefined.comparator);
+  });
+
+  QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) {
+    assert.expect(2);
+    var m = new Backbone.Model({x: 1});
+    var collection = new Backbone.Collection();
+    var opts = {
+      opts: true,
+      success: function(coll, resp, options) {
+        assert.ok(options.opts);
+      }
+    };
+    collection.sync = m.sync = function( method, coll, options ){
+      options.success({});
+    };
+    collection.fetch(opts);
+    collection.create(m, opts);
+  });
+
+  QUnit.test("#1412 - Trigger 'request' and 'sync' events.", function(assert) {
+    assert.expect(4);
+    var collection = new Backbone.Collection;
+    collection.url = '/test';
+    Backbone.ajax = function(settings){ settings.success(); };
+
+    collection.on('request', function(obj, xhr, options) {
+      assert.ok(obj === collection, "collection has correct 'request' event after fetching");
+    });
+    collection.on('sync', function(obj, response, options) {
+      assert.ok(obj === collection, "collection has correct 'sync' event after fetching");
+    });
+    collection.fetch();
+    collection.off();
+
+    collection.on('request', function(obj, xhr, options) {
+      assert.ok(obj === collection.get(1), "collection has correct 'request' event after one of its models save");
+    });
+    collection.on('sync', function(obj, response, options) {
+      assert.ok(obj === collection.get(1), "collection has correct 'sync' event after one of its models save");
+    });
+    collection.create({id: 1});
+    collection.off();
+  });
+
+  QUnit.test('#3283 - fetch, create calls success with context', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection;
+    collection.url = '/test';
+    Backbone.ajax = function(settings) {
+      settings.success.call(settings.context);
+    };
+    var obj = {};
+    var options = {
+      context: obj,
+      success: function() {
+        assert.equal(this, obj);
+      }
+    };
+
+    collection.fetch(options);
+    collection.create({id: 1}, options);
+  });
+
+  QUnit.test('#1447 - create with wait adds model.', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection;
+    var model = new Backbone.Model;
+    model.sync = function(method, m, options){ options.success(); };
+    collection.on('add', function(){ assert.ok(true); });
+    collection.create(model, {wait: true});
+  });
+
+  QUnit.test('#1448 - add sorts collection after merge.', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([
+      {id: 1, x: 1},
+      {id: 2, x: 2}
+    ]);
+    collection.comparator = function(model){ return model.get('x'); };
+    collection.add({id: 1, x: 3}, {merge: true});
+    assert.deepEqual(collection.pluck('id'), [2, 1]);
+  });
+
+  QUnit.test('#1655 - groupBy can be used with a string argument.', function(assert) {
+    assert.expect(3);
+    var collection = new Backbone.Collection([{x: 1}, {x: 2}]);
+    var grouped = collection.groupBy('x');
+    assert.strictEqual(_.keys(grouped).length, 2);
+    assert.strictEqual(grouped[1][0].get('x'), 1);
+    assert.strictEqual(grouped[2][0].get('x'), 2);
+  });
+
+  QUnit.test('#1655 - sortBy can be used with a string argument.', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{x: 3}, {x: 1}, {x: 2}]);
+    var values = _.map(collection.sortBy('x'), function(model) {
+      return model.get('x');
+    });
+    assert.deepEqual(values, [1, 2, 3]);
+  });
+
+  QUnit.test('#1604 - Removal during iteration.', function(assert) {
+    assert.expect(0);
+    var collection = new Backbone.Collection([{}, {}]);
+    collection.on('add', function() {
+      collection.at(0).destroy();
+    });
+    collection.add({}, {at: 0});
+  });
+
+  QUnit.test('#1638 - `sort` during `add` triggers correctly.', function(assert) {
+    var collection = new Backbone.Collection;
+    collection.comparator = function(model) { return model.get('x'); };
+    var added = [];
+    collection.on('add', function(model) {
+      model.set({x: 3});
+      collection.sort();
+      added.push(model.id);
+    });
+    collection.add([{id: 1, x: 1}, {id: 2, x: 2}]);
+    assert.deepEqual(added, [1, 2]);
+  });
+
+  QUnit.test('fetch parses models by default', function(assert) {
+    assert.expect(1);
+    var model = {};
+    var Collection = Backbone.Collection.extend({
+      url: 'test',
+      model: Backbone.Model.extend({
+        parse: function(resp) {
+          assert.strictEqual(resp, model);
+        }
+      })
+    });
+    new Collection().fetch();
+    this.ajaxSettings.success([model]);
+  });
+
+  QUnit.test("`sort` shouldn't always fire on `add`", function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}], {
+      comparator: 'id'
+    });
+    collection.sort = function(){ assert.ok(true); };
+    collection.add([]);
+    collection.add({id: 1});
+    collection.add([{id: 2}, {id: 3}]);
+    collection.add({id: 4});
+  });
+
+  QUnit.test('#1407 parse option on constructor parses collection and models', function(assert) {
+    assert.expect(2);
+    var model = {
+      namespace: [{id: 1}, {id: 2}]
+    };
+    var Collection = Backbone.Collection.extend({
+      model: Backbone.Model.extend({
+        parse: function(m) {
+          m.name = 'test';
+          return m;
+        }
+      }),
+      parse: function(m) {
+        return m.namespace;
+      }
+    });
+    var collection = new Collection(model, {parse: true});
+
+    assert.equal(collection.length, 2);
+    assert.equal(collection.at(0).get('name'), 'test');
+  });
+
+  QUnit.test('#1407 parse option on reset parses collection and models', function(assert) {
+    assert.expect(2);
+    var model = {
+      namespace: [{id: 1}, {id: 2}]
+    };
+    var Collection = Backbone.Collection.extend({
+      model: Backbone.Model.extend({
+        parse: function(m) {
+          m.name = 'test';
+          return m;
+        }
+      }),
+      parse: function(m) {
+        return m.namespace;
+      }
+    });
+    var collection = new Collection();
+    collection.reset(model, {parse: true});
+
+    assert.equal(collection.length, 2);
+    assert.equal(collection.at(0).get('name'), 'test');
+  });
+
+
+  QUnit.test('Reset includes previous models in triggered event.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    var collection = new Backbone.Collection([model]);
+    collection.on('reset', function(coll, options) {
+      assert.deepEqual(options.previousModels, [model]);
+    });
+    collection.reset([]);
+  });
+
+  QUnit.test('set', function(assert) {
+    var m1 = new Backbone.Model();
+    var m2 = new Backbone.Model({id: 2});
+    var m3 = new Backbone.Model();
+    var collection = new Backbone.Collection([m1, m2]);
+
+    // Test add/change/remove events
+    collection.on('add', function(model) {
+      assert.strictEqual(model, m3);
+    });
+    collection.on('change', function(model) {
+      assert.strictEqual(model, m2);
+    });
+    collection.on('remove', function(model) {
+      assert.strictEqual(model, m1);
+    });
+
+    // remove: false doesn't remove any models
+    collection.set([], {remove: false});
+    assert.strictEqual(collection.length, 2);
+
+    // add: false doesn't add any models
+    collection.set([m1, m2, m3], {add: false});
+    assert.strictEqual(collection.length, 2);
+
+    // merge: false doesn't change any models
+    collection.set([m1, {id: 2, a: 1}], {merge: false});
+    assert.strictEqual(m2.get('a'), void 0);
+
+    // add: false, remove: false only merges existing models
+    collection.set([m1, {id: 2, a: 0}, m3, {id: 4}], {add: false, remove: false});
+    assert.strictEqual(collection.length, 2);
+    assert.strictEqual(m2.get('a'), 0);
+
+    // default options add/remove/merge as appropriate
+    collection.set([{id: 2, a: 1}, m3]);
+    assert.strictEqual(collection.length, 2);
+    assert.strictEqual(m2.get('a'), 1);
+
+    // Test removing models not passing an argument
+    collection.off('remove').on('remove', function(model) {
+      assert.ok(model === m2 || model === m3);
+    });
+    collection.set([]);
+    assert.strictEqual(collection.length, 0);
+
+    // Test null models on set doesn't clear collection
+    collection.off();
+    collection.set([{id: 1}]);
+    collection.set();
+    assert.strictEqual(collection.length, 1);
+  });
+
+  QUnit.test('set with only cids', function(assert) {
+    assert.expect(3);
+    var m1 = new Backbone.Model;
+    var m2 = new Backbone.Model;
+    var collection = new Backbone.Collection;
+    collection.set([m1, m2]);
+    assert.equal(collection.length, 2);
+    collection.set([m1]);
+    assert.equal(collection.length, 1);
+    collection.set([m1, m1, m1, m2, m2], {remove: false});
+    assert.equal(collection.length, 2);
+  });
+
+  QUnit.test('set with only idAttribute', function(assert) {
+    assert.expect(3);
+    var m1 = {_id: 1};
+    var m2 = {_id: 2};
+    var Col = Backbone.Collection.extend({
+      model: Backbone.Model.extend({
+        idAttribute: '_id'
+      })
+    });
+    var collection = new Col;
+    collection.set([m1, m2]);
+    assert.equal(collection.length, 2);
+    collection.set([m1]);
+    assert.equal(collection.length, 1);
+    collection.set([m1, m1, m1, m2, m2], {remove: false});
+    assert.equal(collection.length, 2);
+  });
+
+  QUnit.test('set + merge with default values defined', function(assert) {
+    var Model = Backbone.Model.extend({
+      defaults: {
+        key: 'value'
+      }
+    });
+    var m = new Model({id: 1});
+    var collection = new Backbone.Collection([m], {model: Model});
+    assert.equal(collection.first().get('key'), 'value');
+
+    collection.set({id: 1, key: 'other'});
+    assert.equal(collection.first().get('key'), 'other');
+
+    collection.set({id: 1, other: 'value'});
+    assert.equal(collection.first().get('key'), 'other');
+    assert.equal(collection.length, 1);
+  });
+
+  QUnit.test('merge without mutation', function(assert) {
+    var Model = Backbone.Model.extend({
+      initialize: function(attrs, options) {
+        if (attrs.child) {
+          this.set('child', new Model(attrs.child, options), options);
+        }
+      }
+    });
+    var Collection = Backbone.Collection.extend({model: Model});
+    var data = [{id: 1, child: {id: 2}}];
+    var collection = new Collection(data);
+    assert.equal(collection.first().id, 1);
+    collection.set(data);
+    assert.equal(collection.first().id, 1);
+    collection.set([{id: 2, child: {id: 2}}].concat(data));
+    assert.deepEqual(collection.pluck('id'), [2, 1]);
+  });
+
+  QUnit.test('`set` and model level `parse`', function(assert) {
+    var Model = Backbone.Model.extend({});
+    var Collection = Backbone.Collection.extend({
+      model: Model,
+      parse: function(res) { return _.map(res.models, 'model'); }
+    });
+    var model = new Model({id: 1});
+    var collection = new Collection(model);
+    collection.set({models: [
+      {model: {id: 1}},
+      {model: {id: 2}}
+    ]}, {parse: true});
+    assert.equal(collection.first(), model);
+  });
+
+  QUnit.test('`set` data is only parsed once', function(assert) {
+    var collection = new Backbone.Collection();
+    collection.model = Backbone.Model.extend({
+      parse: function(data) {
+        assert.equal(data.parsed, void 0);
+        data.parsed = true;
+        return data;
+      }
+    });
+    collection.set({}, {parse: true});
+  });
+
+  QUnit.test('`set` matches input order in the absence of a comparator', function(assert) {
+    var one = new Backbone.Model({id: 1});
+    var two = new Backbone.Model({id: 2});
+    var three = new Backbone.Model({id: 3});
+    var collection = new Backbone.Collection([one, two, three]);
+    collection.set([{id: 3}, {id: 2}, {id: 1}]);
+    assert.deepEqual(collection.models, [three, two, one]);
+    collection.set([{id: 1}, {id: 2}]);
+    assert.deepEqual(collection.models, [one, two]);
+    collection.set([two, three, one]);
+    assert.deepEqual(collection.models, [two, three, one]);
+    collection.set([{id: 1}, {id: 2}], {remove: false});
+    assert.deepEqual(collection.models, [two, three, one]);
+    collection.set([{id: 1}, {id: 2}, {id: 3}], {merge: false});
+    assert.deepEqual(collection.models, [one, two, three]);
+    collection.set([three, two, one, {id: 4}], {add: false});
+    assert.deepEqual(collection.models, [one, two, three]);
+  });
+
+  QUnit.test('#1894 - Push should not trigger a sort', function(assert) {
+    assert.expect(0);
+    var Collection = Backbone.Collection.extend({
+      comparator: 'id',
+      sort: function() { assert.ok(false); }
+    });
+    new Collection().push({id: 1});
+  });
+
+  QUnit.test('#2428 - push duplicate models, return the correct one', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection;
+    var model1 = collection.push({id: 101});
+    var model2 = collection.push({id: 101});
+    assert.ok(model2.cid === model1.cid);
+  });
+
+  QUnit.test('`set` with non-normal id', function(assert) {
+    var Collection = Backbone.Collection.extend({
+      model: Backbone.Model.extend({idAttribute: '_id'})
+    });
+    var collection = new Collection({_id: 1});
+    collection.set([{_id: 1, a: 1}], {add: false});
+    assert.equal(collection.first().get('a'), 1);
+  });
+
+  QUnit.test('#1894 - `sort` can optionally be turned off', function(assert) {
+    assert.expect(0);
+    var Collection = Backbone.Collection.extend({
+      comparator: 'id',
+      sort: function() { assert.ok(false); }
+    });
+    new Collection().add({id: 1}, {sort: false});
+  });
+
+  QUnit.test('#1915 - `parse` data in the right order in `set`', function(assert) {
+    var collection = new (Backbone.Collection.extend({
+      parse: function(data) {
+        assert.strictEqual(data.status, 'ok');
+        return data.data;
+      }
+    }));
+    var res = {status: 'ok', data: [{id: 1}]};
+    collection.set(res, {parse: true});
+  });
+
+  QUnit.test('#1939 - `parse` is passed `options`', function(assert) {
+    var done = assert.async();
+    assert.expect(1);
+    var collection = new (Backbone.Collection.extend({
+      url: '/',
+      parse: function(data, options) {
+        assert.strictEqual(options.xhr.someHeader, 'headerValue');
+        return data;
+      }
+    }));
+    var ajax = Backbone.ajax;
+    Backbone.ajax = function(params) {
+      _.defer(params.success, []);
+      return {someHeader: 'headerValue'};
+    };
+    collection.fetch({
+      success: function() { done(); }
+    });
+    Backbone.ajax = ajax;
+  });
+
+  QUnit.test('fetch will pass extra options to success callback', function(assert) {
+    assert.expect(1);
+    var SpecialSyncCollection = Backbone.Collection.extend({
+      url: '/test',
+      sync: function(method, collection, options) {
+        _.extend(options, {specialSync: true});
+        return Backbone.Collection.prototype.sync.call(this, method, collection, options);
+      }
+    });
+
+    var collection = new SpecialSyncCollection();
+
+    var onSuccess = function(coll, resp, options) {
+      assert.ok(options.specialSync, 'Options were passed correctly to callback');
+    };
+
+    collection.fetch({success: onSuccess});
+    this.ajaxSettings.success();
+  });
+
+  QUnit.test('`add` only `sort`s when necessary', function(assert) {
+    assert.expect(2);
+    var collection = new (Backbone.Collection.extend({
+      comparator: 'a'
+    }))([{id: 1}, {id: 2}, {id: 3}]);
+    collection.on('sort', function() { assert.ok(true); });
+    collection.add({id: 4}); // do sort, new model
+    collection.add({id: 1, a: 1}, {merge: true}); // do sort, comparator change
+    collection.add({id: 1, b: 1}, {merge: true}); // don't sort, no comparator change
+    collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no comparator change
+    collection.add(collection.models); // don't sort, nothing new
+    collection.add(collection.models, {merge: true}); // don't sort
+  });
+
+  QUnit.test('`add` only `sort`s when necessary with comparator function', function(assert) {
+    assert.expect(3);
+    var collection = new (Backbone.Collection.extend({
+      comparator: function(m1, m2) {
+        return m1.get('a') > m2.get('a') ? 1 : (m1.get('a') < m2.get('a') ? -1 : 0);
+      }
+    }))([{id: 1}, {id: 2}, {id: 3}]);
+    collection.on('sort', function() { assert.ok(true); });
+    collection.add({id: 4}); // do sort, new model
+    collection.add({id: 1, a: 1}, {merge: true}); // do sort, model change
+    collection.add({id: 1, b: 1}, {merge: true}); // do sort, model change
+    collection.add({id: 1, a: 1}, {merge: true}); // don't sort, no model change
+    collection.add(collection.models); // don't sort, nothing new
+    collection.add(collection.models, {merge: true}); // don't sort
+  });
+
+  QUnit.test('Attach options to collection.', function(assert) {
+    assert.expect(2);
+    var Model = Backbone.Model;
+    var comparator = function(){};
+
+    var collection = new Backbone.Collection([], {
+      model: Model,
+      comparator: comparator
+    });
+
+    assert.ok(collection.model === Model);
+    assert.ok(collection.comparator === comparator);
+  });
+
+  QUnit.test('Pass falsey for `models` for empty Col with `options`', function(assert) {
+    assert.expect(9);
+    var opts = {a: 1, b: 2};
+    _.forEach([undefined, null, false], function(falsey) {
+      var Collection = Backbone.Collection.extend({
+        initialize: function(models, options) {
+          assert.strictEqual(models, falsey);
+          assert.strictEqual(options, opts);
+        }
+      });
+
+      var collection = new Collection(falsey, opts);
+      assert.strictEqual(collection.length, 0);
+    });
+  });
+
+  QUnit.test('`add` overrides `set` flags', function(assert) {
+    var collection = new Backbone.Collection();
+    collection.once('add', function(model, coll, options) {
+      coll.add({id: 2}, options);
+    });
+    collection.set({id: 1});
+    assert.equal(collection.length, 2);
+  });
+
+  QUnit.test('#2606 - Collection#create, success arguments', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection;
+    collection.url = 'test';
+    collection.create({}, {
+      success: function(model, resp, options) {
+        assert.strictEqual(resp, 'response');
+      }
+    });
+    this.ajaxSettings.success('response');
+  });
+
+  QUnit.test('#2612 - nested `parse` works with `Collection#set`', function(assert) {
+
+    var Job = Backbone.Model.extend({
+      constructor: function() {
+        this.items = new Items();
+        Backbone.Model.apply(this, arguments);
+      },
+      parse: function(attrs) {
+        this.items.set(attrs.items, {parse: true});
+        return _.omit(attrs, 'items');
+      }
+    });
+
+    var Item = Backbone.Model.extend({
+      constructor: function() {
+        this.subItems = new Backbone.Collection();
+        Backbone.Model.apply(this, arguments);
+      },
+      parse: function(attrs) {
+        this.subItems.set(attrs.subItems, {parse: true});
+        return _.omit(attrs, 'subItems');
+      }
+    });
+
+    var Items = Backbone.Collection.extend({
+      model: Item
+    });
+
+    var data = {
+      name: 'JobName',
+      id: 1,
+      items: [{
+        id: 1,
+        name: 'Sub1',
+        subItems: [
+          {id: 1, subName: 'One'},
+          {id: 2, subName: 'Two'}
+        ]
+      }, {
+        id: 2,
+        name: 'Sub2',
+        subItems: [
+          {id: 3, subName: 'Three'},
+          {id: 4, subName: 'Four'}
+        ]
+      }]
+    };
+
+    var newData = {
+      name: 'NewJobName',
+      id: 1,
+      items: [{
+        id: 1,
+        name: 'NewSub1',
+        subItems: [
+          {id: 1, subName: 'NewOne'},
+          {id: 2, subName: 'NewTwo'}
+        ]
+      }, {
+        id: 2,
+        name: 'NewSub2',
+        subItems: [
+          {id: 3, subName: 'NewThree'},
+          {id: 4, subName: 'NewFour'}
+        ]
+      }]
+    };
+
+    var job = new Job(data, {parse: true});
+    assert.equal(job.get('name'), 'JobName');
+    assert.equal(job.items.at(0).get('name'), 'Sub1');
+    assert.equal(job.items.length, 2);
+    assert.equal(job.items.get(1).subItems.get(1).get('subName'), 'One');
+    assert.equal(job.items.get(2).subItems.get(3).get('subName'), 'Three');
+    job.set(job.parse(newData, {parse: true}));
+    assert.equal(job.get('name'), 'NewJobName');
+    assert.equal(job.items.at(0).get('name'), 'NewSub1');
+    assert.equal(job.items.length, 2);
+    assert.equal(job.items.get(1).subItems.get(1).get('subName'), 'NewOne');
+    assert.equal(job.items.get(2).subItems.get(3).get('subName'), 'NewThree');
+  });
+
+  QUnit.test('_addReference binds all collection events & adds to the lookup hashes', function(assert) {
+    assert.expect(8);
+
+    var calls = {add: 0, remove: 0};
+
+    var Collection = Backbone.Collection.extend({
+
+      _addReference: function(model) {
+        Backbone.Collection.prototype._addReference.apply(this, arguments);
+        calls.add++;
+        assert.equal(model, this._byId[model.id]);
+        assert.equal(model, this._byId[model.cid]);
+        assert.equal(model._events.all.length, 1);
+      },
+
+      _removeReference: function(model) {
+        Backbone.Collection.prototype._removeReference.apply(this, arguments);
+        calls.remove++;
+        assert.equal(this._byId[model.id], void 0);
+        assert.equal(this._byId[model.cid], void 0);
+        assert.equal(model.collection, void 0);
+      }
+
+    });
+
+    var collection = new Collection();
+    var model = collection.add({id: 1});
+    collection.remove(model);
+
+    assert.equal(calls.add, 1);
+    assert.equal(calls.remove, 1);
+  });
+
+  QUnit.test('Do not allow duplicate models to be `add`ed or `set`', function(assert) {
+    var collection = new Backbone.Collection();
+
+    collection.add([{id: 1}, {id: 1}]);
+    assert.equal(collection.length, 1);
+    assert.equal(collection.models.length, 1);
+
+    collection.set([{id: 1}, {id: 1}]);
+    assert.equal(collection.length, 1);
+    assert.equal(collection.models.length, 1);
+  });
+
+  QUnit.test('#3020: #set with {add: false} should not throw.', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection;
+    collection.set([{id: 1}], {add: false});
+    assert.strictEqual(collection.length, 0);
+    assert.strictEqual(collection.models.length, 0);
+  });
+
+  QUnit.test('create with wait, model instance, #3028', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection();
+    var model = new Backbone.Model({id: 1});
+    model.sync = function(){
+      assert.equal(this.collection, collection);
+    };
+    collection.create(model, {wait: true});
+  });
+
+  QUnit.test('modelId', function(assert) {
+    var Stooge = Backbone.Model.extend();
+    var StoogeCollection = Backbone.Collection.extend({model: Stooge});
+
+    // Default to using `Collection::model::idAttribute`.
+    assert.equal(StoogeCollection.prototype.modelId({id: 1}), 1);
+    Stooge.prototype.idAttribute = '_id';
+    assert.equal(StoogeCollection.prototype.modelId({_id: 1}), 1);
+  });
+
+  QUnit.test('Polymorphic models work with "simple" constructors', function(assert) {
+    var A = Backbone.Model.extend();
+    var B = Backbone.Model.extend();
+    var C = Backbone.Collection.extend({
+      model: function(attrs) {
+        return attrs.type === 'a' ? new A(attrs) : new B(attrs);
+      }
+    });
+    var collection = new C([{id: 1, type: 'a'}, {id: 2, type: 'b'}]);
+    assert.equal(collection.length, 2);
+    assert.ok(collection.at(0) instanceof A);
+    assert.equal(collection.at(0).id, 1);
+    assert.ok(collection.at(1) instanceof B);
+    assert.equal(collection.at(1).id, 2);
+  });
+
+  QUnit.test('Polymorphic models work with "advanced" constructors', function(assert) {
+    var A = Backbone.Model.extend({idAttribute: '_id'});
+    var B = Backbone.Model.extend({idAttribute: '_id'});
+    var C = Backbone.Collection.extend({
+      model: Backbone.Model.extend({
+        constructor: function(attrs) {
+          return attrs.type === 'a' ? new A(attrs) : new B(attrs);
+        },
+
+        idAttribute: '_id'
+      })
+    });
+    var collection = new C([{_id: 1, type: 'a'}, {_id: 2, type: 'b'}]);
+    assert.equal(collection.length, 2);
+    assert.ok(collection.at(0) instanceof A);
+    assert.equal(collection.at(0), collection.get(1));
+    assert.ok(collection.at(1) instanceof B);
+    assert.equal(collection.at(1), collection.get(2));
+
+    C = Backbone.Collection.extend({
+      model: function(attrs) {
+        return attrs.type === 'a' ? new A(attrs) : new B(attrs);
+      },
+
+      modelId: function(attrs) {
+        return attrs.type + '-' + attrs.id;
+      }
+    });
+    collection = new C([{id: 1, type: 'a'}, {id: 1, type: 'b'}]);
+    assert.equal(collection.length, 2);
+    assert.ok(collection.at(0) instanceof A);
+    assert.equal(collection.at(0), collection.get('a-1'));
+    assert.ok(collection.at(1) instanceof B);
+    assert.equal(collection.at(1), collection.get('b-1'));
+  });
+
+  QUnit.test('Collection with polymorphic models receives default id from modelId', function(assert) {
+    assert.expect(6);
+    // When the polymorphic models use 'id' for the idAttribute, all is fine.
+    var C1 = Backbone.Collection.extend({
+      model: function(attrs) {
+        return new Backbone.Model(attrs);
+      }
+    });
+    var c1 = new C1({id: 1});
+    assert.equal(c1.get(1).id, 1);
+    assert.equal(c1.modelId({id: 1}), 1);
+
+    // If the polymorphic models define their own idAttribute,
+    // the modelId method should be overridden, for the reason below.
+    var M = Backbone.Model.extend({
+      idAttribute: '_id'
+    });
+    var C2 = Backbone.Collection.extend({
+      model: function(attrs) {
+        return new M(attrs);
+      }
+    });
+    var c2 = new C2({'_id': 1});
+    assert.equal(c2.get(1), void 0);
+    assert.equal(c2.modelId(c2.at(0).attributes), void 0);
+    var m = new M({'_id': 2});
+    c2.add(m);
+    assert.equal(c2.get(2), void 0);
+    assert.equal(c2.modelId(m.attributes), void 0);
+  });
+
+  QUnit.test('#3039 #3951: adding at index fires with correct at', function(assert) {
+    assert.expect(4);
+    var collection = new Backbone.Collection([{val: 0}, {val: 4}]);
+    collection.on('add', function(model, coll, options) {
+      assert.equal(model.get('val'), options.index);
+    });
+    collection.add([{val: 1}, {val: 2}, {val: 3}], {at: 1});
+    collection.add({val: 5}, {at: 10});
+  });
+
+  QUnit.test('#3039: index is not sent when at is not specified', function(assert) {
+    assert.expect(2);
+    var collection = new Backbone.Collection([{at: 0}]);
+    collection.on('add', function(model, coll, options) {
+      assert.equal(undefined, options.index);
+    });
+    collection.add([{at: 1}, {at: 2}]);
+  });
+
+  QUnit.test('#3199 - Order changing should trigger a sort', function(assert) {
+    assert.expect(1);
+    var one = new Backbone.Model({id: 1});
+    var two = new Backbone.Model({id: 2});
+    var three = new Backbone.Model({id: 3});
+    var collection = new Backbone.Collection([one, two, three]);
+    collection.on('sort', function() {
+      assert.ok(true);
+    });
+    collection.set([{id: 3}, {id: 2}, {id: 1}]);
+  });
+
+  QUnit.test('#3199 - Adding a model should trigger a sort', function(assert) {
+    assert.expect(1);
+    var one = new Backbone.Model({id: 1});
+    var two = new Backbone.Model({id: 2});
+    var three = new Backbone.Model({id: 3});
+    var collection = new Backbone.Collection([one, two, three]);
+    collection.on('sort', function() {
+      assert.ok(true);
+    });
+    collection.set([{id: 1}, {id: 2}, {id: 3}, {id: 0}]);
+  });
+
+  QUnit.test('#3199 - Order not changing should not trigger a sort', function(assert) {
+    assert.expect(0);
+    var one = new Backbone.Model({id: 1});
+    var two = new Backbone.Model({id: 2});
+    var three = new Backbone.Model({id: 3});
+    var collection = new Backbone.Collection([one, two, three]);
+    collection.on('sort', function() {
+      assert.ok(false);
+    });
+    collection.set([{id: 1}, {id: 2}, {id: 3}]);
+  });
+
+  QUnit.test('add supports negative indexes', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 1}]);
+    collection.add([{id: 2}, {id: 3}], {at: -1});
+    collection.add([{id: 2.5}], {at: -2});
+    collection.add([{id: 0.5}], {at: -6});
+    assert.equal(collection.pluck('id').join(','), '0.5,1,2,2.5,3');
+  });
+
+  QUnit.test('#set accepts options.at as a string', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
+    collection.add([{id: 3}], {at: '1'});
+    assert.deepEqual(collection.pluck('id'), [1, 3, 2]);
+  });
+
+  QUnit.test('adding multiple models triggers `update` event once', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection;
+    collection.on('update', function() { assert.ok(true); });
+    collection.add([{id: 1}, {id: 2}, {id: 3}]);
+  });
+
+  QUnit.test('removing models triggers `update` event once', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}, {id: 3}]);
+    collection.on('update', function() { assert.ok(true); });
+    collection.remove([{id: 1}, {id: 2}]);
+  });
+
+  QUnit.test('remove does not trigger `update` when nothing removed', function(assert) {
+    assert.expect(0);
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
+    collection.on('update', function() { assert.ok(false); });
+    collection.remove([{id: 3}]);
+  });
+
+  QUnit.test('set triggers `set` event once', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
+    collection.on('update', function() { assert.ok(true); });
+    collection.set([{id: 1}, {id: 3}]);
+  });
+
+  QUnit.test('set does not trigger `update` event when nothing added nor removed', function(assert) {
+    var collection = new Backbone.Collection([{id: 1}, {id: 2}]);
+    collection.on('update', function(coll, options) {
+      assert.equal(options.changes.added.length, 0);
+      assert.equal(options.changes.removed.length, 0);
+      assert.equal(options.changes.merged.length, 2);
+    });
+    collection.set([{id: 1}, {id: 2}]);
+  });
+
+  QUnit.test('#3610 - invoke collects arguments', function(assert) {
+    assert.expect(3);
+    var Model = Backbone.Model.extend({
+      method: function(x, y, z) {
+        assert.equal(x, 1);
+        assert.equal(y, 2);
+        assert.equal(z, 3);
+      }
+    });
+    var Collection = Backbone.Collection.extend({
+      model: Model
+    });
+    var collection = new Collection([{id: 1}]);
+    collection.invoke('method', 1, 2, 3);
+  });
+
+  QUnit.test('#3662 - triggering change without model will not error', function(assert) {
+    assert.expect(1);
+    var collection = new Backbone.Collection([{id: 1}]);
+    var model = collection.first();
+    collection.on('change', function(m) {
+      assert.equal(m, undefined);
+    });
+    model.trigger('change');
+  });
+
+  QUnit.test('#3871 - falsy parse result creates empty collection', function(assert) {
+    var collection = new (Backbone.Collection.extend({
+      parse: function(data, options) {}
+    }));
+    collection.set('', {parse: true});
+    assert.equal(collection.length, 0);
+  });
+
+  QUnit.test("#3711 - remove's `update` event returns one removed model", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var collection = new Backbone.Collection([model]);
+    collection.on('update', function(context, options) {
+      var changed = options.changes;
+      assert.deepEqual(changed.added, []);
+      assert.deepEqual(changed.merged, []);
+      assert.strictEqual(changed.removed[0], model);
+    });
+    collection.remove(model);
+  });
+
+  QUnit.test("#3711 - remove's `update` event returns multiple removed models", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var model2 = new Backbone.Model({id: 2, title: 'Second Post'});
+    var collection = new Backbone.Collection([model, model2]);
+    collection.on('update', function(context, options) {
+      var changed = options.changes;
+      assert.deepEqual(changed.added, []);
+      assert.deepEqual(changed.merged, []);
+      assert.ok(changed.removed.length === 2);
+
+      assert.ok(_.indexOf(changed.removed, model) > -1 && _.indexOf(changed.removed, model2) > -1);
+    });
+    collection.remove([model, model2]);
+  });
+
+  QUnit.test("#3711 - set's `update` event returns one added model", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var collection = new Backbone.Collection();
+    collection.on('update', function(context, options) {
+      var addedModels = options.changes.added;
+      assert.ok(addedModels.length === 1);
+      assert.strictEqual(addedModels[0], model);
+    });
+    collection.set(model);
+  });
+
+  QUnit.test("#3711 - set's `update` event returns multiple added models", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var model2 = new Backbone.Model({id: 2, title: 'Second Post'});
+    var collection = new Backbone.Collection();
+    collection.on('update', function(context, options) {
+      var addedModels = options.changes.added;
+      assert.ok(addedModels.length === 2);
+      assert.strictEqual(addedModels[0], model);
+      assert.strictEqual(addedModels[1], model2);
+    });
+    collection.set([model, model2]);
+  });
+
+  QUnit.test("#3711 - set's `update` event returns one removed model", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var model2 = new Backbone.Model({id: 2, title: 'Second Post'});
+    var model3 = new Backbone.Model({id: 3, title: 'My Last Post'});
+    var collection = new Backbone.Collection([model]);
+    collection.on('update', function(context, options) {
+      var changed = options.changes;
+      assert.equal(changed.added.length, 2);
+      assert.equal(changed.merged.length, 0);
+      assert.ok(changed.removed.length === 1);
+      assert.strictEqual(changed.removed[0], model);
+    });
+    collection.set([model2, model3]);
+  });
+
+  QUnit.test("#3711 - set's `update` event returns multiple removed models", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var model2 = new Backbone.Model({id: 2, title: 'Second Post'});
+    var model3 = new Backbone.Model({id: 3, title: 'My Last Post'});
+    var collection = new Backbone.Collection([model, model2]);
+    collection.on('update', function(context, options) {
+      var removedModels = options.changes.removed;
+      assert.ok(removedModels.length === 2);
+      assert.strictEqual(removedModels[0], model);
+      assert.strictEqual(removedModels[1], model2);
+    });
+    collection.set([model3]);
+  });
+
+  QUnit.test("#3711 - set's `update` event returns one merged model", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var model2 = new Backbone.Model({id: 2, title: 'Second Post'});
+    var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'});
+    var collection = new Backbone.Collection([model, model2]);
+    collection.on('update', function(context, options) {
+      var mergedModels = options.changes.merged;
+      assert.ok(mergedModels.length === 1);
+      assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title'));
+    });
+    collection.set([model2Update]);
+  });
+
+  QUnit.test("#3711 - set's `update` event returns multiple merged models", function(assert) {
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var modelUpdate = new Backbone.Model({id: 1, title: 'First Post V2'});
+    var model2 = new Backbone.Model({id: 2, title: 'Second Post'});
+    var model2Update = new Backbone.Model({id: 2, title: 'Second Post V2'});
+    var collection = new Backbone.Collection([model, model2]);
+    collection.on('update', function(context, options) {
+      var mergedModels = options.changes.merged;
+      assert.ok(mergedModels.length === 2);
+      assert.strictEqual(mergedModels[0].get('title'), model2Update.get('title'));
+      assert.strictEqual(mergedModels[1].get('title'), modelUpdate.get('title'));
+    });
+    collection.set([model2Update, modelUpdate]);
+  });
+
+  QUnit.test("#3711 - set's `update` event should not be triggered adding a model which already exists exactly alike", function(assert) {
+    var fired = false;
+    var model = new Backbone.Model({id: 1, title: 'First Post'});
+    var collection = new Backbone.Collection([model]);
+    collection.on('update', function(context, options) {
+      fired = true;
+    });
+    collection.set([model]);
+    assert.equal(fired, false);
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/events.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/events.js
new file mode 100644
index 0000000..544b39a
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/events.js
@@ -0,0 +1,706 @@
+(function() {
+
+  QUnit.module('Backbone.Events');
+
+  QUnit.test('on and trigger', function(assert) {
+    assert.expect(2);
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+    obj.on('event', function() { obj.counter += 1; });
+    obj.trigger('event');
+    assert.equal(obj.counter, 1, 'counter should be incremented.');
+    obj.trigger('event');
+    obj.trigger('event');
+    obj.trigger('event');
+    obj.trigger('event');
+    assert.equal(obj.counter, 5, 'counter should be incremented five times.');
+  });
+
+  QUnit.test('binding and triggering multiple events', function(assert) {
+    assert.expect(4);
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+
+    obj.on('a b c', function() { obj.counter += 1; });
+
+    obj.trigger('a');
+    assert.equal(obj.counter, 1);
+
+    obj.trigger('a b');
+    assert.equal(obj.counter, 3);
+
+    obj.trigger('c');
+    assert.equal(obj.counter, 4);
+
+    obj.off('a c');
+    obj.trigger('a b c');
+    assert.equal(obj.counter, 5);
+  });
+
+  QUnit.test('binding and triggering with event maps', function(assert) {
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+
+    var increment = function() {
+      this.counter += 1;
+    };
+
+    obj.on({
+      a: increment,
+      b: increment,
+      c: increment
+    }, obj);
+
+    obj.trigger('a');
+    assert.equal(obj.counter, 1);
+
+    obj.trigger('a b');
+    assert.equal(obj.counter, 3);
+
+    obj.trigger('c');
+    assert.equal(obj.counter, 4);
+
+    obj.off({
+      a: increment,
+      c: increment
+    }, obj);
+    obj.trigger('a b c');
+    assert.equal(obj.counter, 5);
+  });
+
+  QUnit.test('binding and triggering multiple event names with event maps', function(assert) {
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+
+    var increment = function() {
+      this.counter += 1;
+    };
+
+    obj.on({
+      'a b c': increment
+    });
+
+    obj.trigger('a');
+    assert.equal(obj.counter, 1);
+
+    obj.trigger('a b');
+    assert.equal(obj.counter, 3);
+
+    obj.trigger('c');
+    assert.equal(obj.counter, 4);
+
+    obj.off({
+      'a c': increment
+    });
+    obj.trigger('a b c');
+    assert.equal(obj.counter, 5);
+  });
+
+  QUnit.test('binding and trigger with event maps context', function(assert) {
+    assert.expect(2);
+    var obj = {counter: 0};
+    var context = {};
+    _.extend(obj, Backbone.Events);
+
+    obj.on({
+      a: function() {
+        assert.strictEqual(this, context, 'defaults `context` to `callback` param');
+      }
+    }, context).trigger('a');
+
+    obj.off().on({
+      a: function() {
+        assert.strictEqual(this, context, 'will not override explicit `context` param');
+      }
+    }, this, context).trigger('a');
+  });
+
+  QUnit.test('listenTo and stopListening', function(assert) {
+    assert.expect(1);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenTo(b, 'all', function(){ assert.ok(true); });
+    b.trigger('anything');
+    a.listenTo(b, 'all', function(){ assert.ok(false); });
+    a.stopListening();
+    b.trigger('anything');
+  });
+
+  QUnit.test('listenTo and stopListening with event maps', function(assert) {
+    assert.expect(4);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    var cb = function(){ assert.ok(true); };
+    a.listenTo(b, {event: cb});
+    b.trigger('event');
+    a.listenTo(b, {event2: cb});
+    b.on('event2', cb);
+    a.stopListening(b, {event2: cb});
+    b.trigger('event event2');
+    a.stopListening();
+    b.trigger('event event2');
+  });
+
+  QUnit.test('stopListening with omitted args', function(assert) {
+    assert.expect(2);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    var cb = function() { assert.ok(true); };
+    a.listenTo(b, 'event', cb);
+    b.on('event', cb);
+    a.listenTo(b, 'event2', cb);
+    a.stopListening(null, {event: cb});
+    b.trigger('event event2');
+    b.off();
+    a.listenTo(b, 'event event2', cb);
+    a.stopListening(null, 'event');
+    a.stopListening();
+    b.trigger('event2');
+  });
+
+  QUnit.test('listenToOnce', function(assert) {
+    assert.expect(2);
+    // Same as the previous test, but we use once rather than having to explicitly unbind
+    var obj = {counterA: 0, counterB: 0};
+    _.extend(obj, Backbone.Events);
+    var incrA = function(){ obj.counterA += 1; obj.trigger('event'); };
+    var incrB = function(){ obj.counterB += 1; };
+    obj.listenToOnce(obj, 'event', incrA);
+    obj.listenToOnce(obj, 'event', incrB);
+    obj.trigger('event');
+    assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.');
+    assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.');
+  });
+
+  QUnit.test('listenToOnce and stopListening', function(assert) {
+    assert.expect(1);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenToOnce(b, 'all', function() { assert.ok(true); });
+    b.trigger('anything');
+    b.trigger('anything');
+    a.listenToOnce(b, 'all', function() { assert.ok(false); });
+    a.stopListening();
+    b.trigger('anything');
+  });
+
+  QUnit.test('listenTo, listenToOnce and stopListening', function(assert) {
+    assert.expect(1);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenToOnce(b, 'all', function() { assert.ok(true); });
+    b.trigger('anything');
+    b.trigger('anything');
+    a.listenTo(b, 'all', function() { assert.ok(false); });
+    a.stopListening();
+    b.trigger('anything');
+  });
+
+  QUnit.test('listenTo and stopListening with event maps', function(assert) {
+    assert.expect(1);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenTo(b, {change: function(){ assert.ok(true); }});
+    b.trigger('change');
+    a.listenTo(b, {change: function(){ assert.ok(false); }});
+    a.stopListening();
+    b.trigger('change');
+  });
+
+  QUnit.test('listenTo yourself', function(assert) {
+    assert.expect(1);
+    var e = _.extend({}, Backbone.Events);
+    e.listenTo(e, 'foo', function(){ assert.ok(true); });
+    e.trigger('foo');
+  });
+
+  QUnit.test('listenTo yourself cleans yourself up with stopListening', function(assert) {
+    assert.expect(1);
+    var e = _.extend({}, Backbone.Events);
+    e.listenTo(e, 'foo', function(){ assert.ok(true); });
+    e.trigger('foo');
+    e.stopListening();
+    e.trigger('foo');
+  });
+
+  QUnit.test('stopListening cleans up references', function(assert) {
+    assert.expect(12);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    var fn = function() {};
+    b.on('event', fn);
+    a.listenTo(b, 'event', fn).stopListening();
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenTo(b, 'event', fn).stopListening(b);
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenTo(b, 'event', fn).stopListening(b, 'event');
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenTo(b, 'event', fn).stopListening(b, 'event', fn);
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+  });
+
+  QUnit.test('stopListening cleans up references from listenToOnce', function(assert) {
+    assert.expect(12);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    var fn = function() {};
+    b.on('event', fn);
+    a.listenToOnce(b, 'event', fn).stopListening();
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenToOnce(b, 'event', fn).stopListening(b);
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenToOnce(b, 'event', fn).stopListening(b, 'event');
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenToOnce(b, 'event', fn).stopListening(b, 'event', fn);
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._events.event), 1);
+    assert.equal(_.size(b._listeners), 0);
+  });
+
+  QUnit.test('listenTo and off cleaning up references', function(assert) {
+    assert.expect(8);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    var fn = function() {};
+    a.listenTo(b, 'event', fn);
+    b.off();
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenTo(b, 'event', fn);
+    b.off('event');
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenTo(b, 'event', fn);
+    b.off(null, fn);
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._listeners), 0);
+    a.listenTo(b, 'event', fn);
+    b.off(null, null, a);
+    assert.equal(_.size(a._listeningTo), 0);
+    assert.equal(_.size(b._listeners), 0);
+  });
+
+  QUnit.test('listenTo and stopListening cleaning up references', function(assert) {
+    assert.expect(2);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenTo(b, 'all', function(){ assert.ok(true); });
+    b.trigger('anything');
+    a.listenTo(b, 'other', function(){ assert.ok(false); });
+    a.stopListening(b, 'other');
+    a.stopListening(b, 'all');
+    assert.equal(_.size(a._listeningTo), 0);
+  });
+
+  QUnit.test('listenToOnce without context cleans up references after the event has fired', function(assert) {
+    assert.expect(2);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenToOnce(b, 'all', function(){ assert.ok(true); });
+    b.trigger('anything');
+    assert.equal(_.size(a._listeningTo), 0);
+  });
+
+  QUnit.test('listenToOnce with event maps cleans up references', function(assert) {
+    assert.expect(2);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenToOnce(b, {
+      one: function() { assert.ok(true); },
+      two: function() { assert.ok(false); }
+    });
+    b.trigger('one');
+    assert.equal(_.size(a._listeningTo), 1);
+  });
+
+  QUnit.test('listenToOnce with event maps binds the correct `this`', function(assert) {
+    assert.expect(1);
+    var a = _.extend({}, Backbone.Events);
+    var b = _.extend({}, Backbone.Events);
+    a.listenToOnce(b, {
+      one: function() { assert.ok(this === a); },
+      two: function() { assert.ok(false); }
+    });
+    b.trigger('one');
+  });
+
+  QUnit.test("listenTo with empty callback doesn't throw an error", function(assert) {
+    assert.expect(1);
+    var e = _.extend({}, Backbone.Events);
+    e.listenTo(e, 'foo', null);
+    e.trigger('foo');
+    assert.ok(true);
+  });
+
+  QUnit.test('trigger all for each event', function(assert) {
+    assert.expect(3);
+    var a, b, obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+    obj.on('all', function(event) {
+      obj.counter++;
+      if (event === 'a') a = true;
+      if (event === 'b') b = true;
+    })
+    .trigger('a b');
+    assert.ok(a);
+    assert.ok(b);
+    assert.equal(obj.counter, 2);
+  });
+
+  QUnit.test('on, then unbind all functions', function(assert) {
+    assert.expect(1);
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+    var callback = function() { obj.counter += 1; };
+    obj.on('event', callback);
+    obj.trigger('event');
+    obj.off('event');
+    obj.trigger('event');
+    assert.equal(obj.counter, 1, 'counter should have only been incremented once.');
+  });
+
+  QUnit.test('bind two callbacks, unbind only one', function(assert) {
+    assert.expect(2);
+    var obj = {counterA: 0, counterB: 0};
+    _.extend(obj, Backbone.Events);
+    var callback = function() { obj.counterA += 1; };
+    obj.on('event', callback);
+    obj.on('event', function() { obj.counterB += 1; });
+    obj.trigger('event');
+    obj.off('event', callback);
+    obj.trigger('event');
+    assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.');
+    assert.equal(obj.counterB, 2, 'counterB should have been incremented twice.');
+  });
+
+  QUnit.test('unbind a callback in the midst of it firing', function(assert) {
+    assert.expect(1);
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+    var callback = function() {
+      obj.counter += 1;
+      obj.off('event', callback);
+    };
+    obj.on('event', callback);
+    obj.trigger('event');
+    obj.trigger('event');
+    obj.trigger('event');
+    assert.equal(obj.counter, 1, 'the callback should have been unbound.');
+  });
+
+  QUnit.test('two binds that unbind themeselves', function(assert) {
+    assert.expect(2);
+    var obj = {counterA: 0, counterB: 0};
+    _.extend(obj, Backbone.Events);
+    var incrA = function(){ obj.counterA += 1; obj.off('event', incrA); };
+    var incrB = function(){ obj.counterB += 1; obj.off('event', incrB); };
+    obj.on('event', incrA);
+    obj.on('event', incrB);
+    obj.trigger('event');
+    obj.trigger('event');
+    obj.trigger('event');
+    assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.');
+    assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.');
+  });
+
+  QUnit.test('bind a callback with a default context when none supplied', function(assert) {
+    assert.expect(1);
+    var obj = _.extend({
+      assertTrue: function() {
+        assert.equal(this, obj, '`this` was bound to the callback');
+      }
+    }, Backbone.Events);
+
+    obj.once('event', obj.assertTrue);
+    obj.trigger('event');
+  });
+
+  QUnit.test('bind a callback with a supplied context', function(assert) {
+    assert.expect(1);
+    var TestClass = function() {
+      return this;
+    };
+    TestClass.prototype.assertTrue = function() {
+      assert.ok(true, '`this` was bound to the callback');
+    };
+
+    var obj = _.extend({}, Backbone.Events);
+    obj.on('event', function() { this.assertTrue(); }, new TestClass);
+    obj.trigger('event');
+  });
+
+  QUnit.test('nested trigger with unbind', function(assert) {
+    assert.expect(1);
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+    var incr1 = function(){ obj.counter += 1; obj.off('event', incr1); obj.trigger('event'); };
+    var incr2 = function(){ obj.counter += 1; };
+    obj.on('event', incr1);
+    obj.on('event', incr2);
+    obj.trigger('event');
+    assert.equal(obj.counter, 3, 'counter should have been incremented three times');
+  });
+
+  QUnit.test('callback list is not altered during trigger', function(assert) {
+    assert.expect(2);
+    var counter = 0, obj = _.extend({}, Backbone.Events);
+    var incr = function(){ counter++; };
+    var incrOn = function(){ obj.on('event all', incr); };
+    var incrOff = function(){ obj.off('event all', incr); };
+
+    obj.on('event all', incrOn).trigger('event');
+    assert.equal(counter, 0, 'on does not alter callback list');
+
+    obj.off().on('event', incrOff).on('event all', incr).trigger('event');
+    assert.equal(counter, 2, 'off does not alter callback list');
+  });
+
+  QUnit.test("#1282 - 'all' callback list is retrieved after each event.", function(assert) {
+    assert.expect(1);
+    var counter = 0;
+    var obj = _.extend({}, Backbone.Events);
+    var incr = function(){ counter++; };
+    obj.on('x', function() {
+      obj.on('y', incr).on('all', incr);
+    })
+    .trigger('x y');
+    assert.strictEqual(counter, 2);
+  });
+
+  QUnit.test('if no callback is provided, `on` is a noop', function(assert) {
+    assert.expect(0);
+    _.extend({}, Backbone.Events).on('test').trigger('test');
+  });
+
+  QUnit.test('if callback is truthy but not a function, `on` should throw an error just like jQuery', function(assert) {
+    assert.expect(1);
+    var view = _.extend({}, Backbone.Events).on('test', 'noop');
+    assert.raises(function() {
+      view.trigger('test');
+    });
+  });
+
+  QUnit.test('remove all events for a specific context', function(assert) {
+    assert.expect(4);
+    var obj = _.extend({}, Backbone.Events);
+    obj.on('x y all', function() { assert.ok(true); });
+    obj.on('x y all', function() { assert.ok(false); }, obj);
+    obj.off(null, null, obj);
+    obj.trigger('x y');
+  });
+
+  QUnit.test('remove all events for a specific callback', function(assert) {
+    assert.expect(4);
+    var obj = _.extend({}, Backbone.Events);
+    var success = function() { assert.ok(true); };
+    var fail = function() { assert.ok(false); };
+    obj.on('x y all', success);
+    obj.on('x y all', fail);
+    obj.off(null, fail);
+    obj.trigger('x y');
+  });
+
+  QUnit.test('#1310 - off does not skip consecutive events', function(assert) {
+    assert.expect(0);
+    var obj = _.extend({}, Backbone.Events);
+    obj.on('event', function() { assert.ok(false); }, obj);
+    obj.on('event', function() { assert.ok(false); }, obj);
+    obj.off(null, null, obj);
+    obj.trigger('event');
+  });
+
+  QUnit.test('once', function(assert) {
+    assert.expect(2);
+    // Same as the previous test, but we use once rather than having to explicitly unbind
+    var obj = {counterA: 0, counterB: 0};
+    _.extend(obj, Backbone.Events);
+    var incrA = function(){ obj.counterA += 1; obj.trigger('event'); };
+    var incrB = function(){ obj.counterB += 1; };
+    obj.once('event', incrA);
+    obj.once('event', incrB);
+    obj.trigger('event');
+    assert.equal(obj.counterA, 1, 'counterA should have only been incremented once.');
+    assert.equal(obj.counterB, 1, 'counterB should have only been incremented once.');
+  });
+
+  QUnit.test('once variant one', function(assert) {
+    assert.expect(3);
+    var f = function(){ assert.ok(true); };
+
+    var a = _.extend({}, Backbone.Events).once('event', f);
+    var b = _.extend({}, Backbone.Events).on('event', f);
+
+    a.trigger('event');
+
+    b.trigger('event');
+    b.trigger('event');
+  });
+
+  QUnit.test('once variant two', function(assert) {
+    assert.expect(3);
+    var f = function(){ assert.ok(true); };
+    var obj = _.extend({}, Backbone.Events);
+
+    obj
+      .once('event', f)
+      .on('event', f)
+      .trigger('event')
+      .trigger('event');
+  });
+
+  QUnit.test('once with off', function(assert) {
+    assert.expect(0);
+    var f = function(){ assert.ok(true); };
+    var obj = _.extend({}, Backbone.Events);
+
+    obj.once('event', f);
+    obj.off('event', f);
+    obj.trigger('event');
+  });
+
+  QUnit.test('once with event maps', function(assert) {
+    var obj = {counter: 0};
+    _.extend(obj, Backbone.Events);
+
+    var increment = function() {
+      this.counter += 1;
+    };
+
+    obj.once({
+      a: increment,
+      b: increment,
+      c: increment
+    }, obj);
+
+    obj.trigger('a');
+    assert.equal(obj.counter, 1);
+
+    obj.trigger('a b');
+    assert.equal(obj.counter, 2);
+
+    obj.trigger('c');
+    assert.equal(obj.counter, 3);
+
+    obj.trigger('a b c');
+    assert.equal(obj.counter, 3);
+  });
+
+  QUnit.test('bind a callback with a supplied context using once with object notation', function(assert) {
+    assert.expect(1);
+    var obj = {counter: 0};
+    var context = {};
+    _.extend(obj, Backbone.Events);
+
+    obj.once({
+      a: function() {
+        assert.strictEqual(this, context, 'defaults `context` to `callback` param');
+      }
+    }, context).trigger('a');
+  });
+
+  QUnit.test('once with off only by context', function(assert) {
+    assert.expect(0);
+    var context = {};
+    var obj = _.extend({}, Backbone.Events);
+    obj.once('event', function(){ assert.ok(false); }, context);
+    obj.off(null, null, context);
+    obj.trigger('event');
+  });
+
+  QUnit.test('Backbone object inherits Events', function(assert) {
+    assert.ok(Backbone.on === Backbone.Events.on);
+  });
+
+  QUnit.test('once with asynchronous events', function(assert) {
+    var done = assert.async();
+    assert.expect(1);
+    var func = _.debounce(function() { assert.ok(true); done(); }, 50);
+    var obj = _.extend({}, Backbone.Events).once('async', func);
+
+    obj.trigger('async');
+    obj.trigger('async');
+  });
+
+  QUnit.test('once with multiple events.', function(assert) {
+    assert.expect(2);
+    var obj = _.extend({}, Backbone.Events);
+    obj.once('x y', function() { assert.ok(true); });
+    obj.trigger('x y');
+  });
+
+  QUnit.test('Off during iteration with once.', function(assert) {
+    assert.expect(2);
+    var obj = _.extend({}, Backbone.Events);
+    var f = function(){ this.off('event', f); };
+    obj.on('event', f);
+    obj.once('event', function(){});
+    obj.on('event', function(){ assert.ok(true); });
+
+    obj.trigger('event');
+    obj.trigger('event');
+  });
+
+  QUnit.test('`once` on `all` should work as expected', function(assert) {
+    assert.expect(1);
+    Backbone.once('all', function() {
+      assert.ok(true);
+      Backbone.trigger('all');
+    });
+    Backbone.trigger('all');
+  });
+
+  QUnit.test('once without a callback is a noop', function(assert) {
+    assert.expect(0);
+    _.extend({}, Backbone.Events).once('event').trigger('event');
+  });
+
+  QUnit.test('listenToOnce without a callback is a noop', function(assert) {
+    assert.expect(0);
+    var obj = _.extend({}, Backbone.Events);
+    obj.listenToOnce(obj, 'event').trigger('event');
+  });
+
+  QUnit.test('event functions are chainable', function(assert) {
+    var obj = _.extend({}, Backbone.Events);
+    var obj2 = _.extend({}, Backbone.Events);
+    var fn = function() {};
+    assert.equal(obj, obj.trigger('noeventssetyet'));
+    assert.equal(obj, obj.off('noeventssetyet'));
+    assert.equal(obj, obj.stopListening('noeventssetyet'));
+    assert.equal(obj, obj.on('a', fn));
+    assert.equal(obj, obj.once('c', fn));
+    assert.equal(obj, obj.trigger('a'));
+    assert.equal(obj, obj.listenTo(obj2, 'a', fn));
+    assert.equal(obj, obj.listenToOnce(obj2, 'b', fn));
+    assert.equal(obj, obj.off('a c'));
+    assert.equal(obj, obj.stopListening(obj2, 'a'));
+    assert.equal(obj, obj.stopListening());
+  });
+
+  QUnit.test('#3448 - listenToOnce with space-separated events', function(assert) {
+    assert.expect(2);
+    var one = _.extend({}, Backbone.Events);
+    var two = _.extend({}, Backbone.Events);
+    var count = 1;
+    one.listenToOnce(two, 'x y', function(n) { assert.ok(n === count++); });
+    two.trigger('x', 1);
+    two.trigger('x', 1);
+    two.trigger('y', 2);
+    two.trigger('y', 2);
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/model.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/model.js
new file mode 100644
index 0000000..b73a1c7
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/model.js
@@ -0,0 +1,1418 @@
+(function() {
+
+  var ProxyModel = Backbone.Model.extend();
+  var Klass = Backbone.Collection.extend({
+    url: function() { return '/collection'; }
+  });
+  var doc, collection;
+
+  QUnit.module('Backbone.Model', {
+
+    beforeEach: function(assert) {
+      doc = new ProxyModel({
+        id: '1-the-tempest',
+        title: 'The Tempest',
+        author: 'Bill Shakespeare',
+        length: 123
+      });
+      collection = new Klass();
+      collection.add(doc);
+    }
+
+  });
+
+  QUnit.test('initialize', function(assert) {
+    assert.expect(3);
+    var Model = Backbone.Model.extend({
+      initialize: function() {
+        this.one = 1;
+        assert.equal(this.collection, collection);
+      }
+    });
+    var model = new Model({}, {collection: collection});
+    assert.equal(model.one, 1);
+    assert.equal(model.collection, collection);
+  });
+
+  QUnit.test('Object.prototype properties are overridden by attributes', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({hasOwnProperty: true});
+    assert.equal(model.get('hasOwnProperty'), true);
+  });
+
+  QUnit.test('initialize with attributes and options', function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      initialize: function(attributes, options) {
+        this.one = options.one;
+      }
+    });
+    var model = new Model({}, {one: 1});
+    assert.equal(model.one, 1);
+  });
+
+  QUnit.test('initialize with parsed attributes', function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      parse: function(attrs) {
+        attrs.value += 1;
+        return attrs;
+      }
+    });
+    var model = new Model({value: 1}, {parse: true});
+    assert.equal(model.get('value'), 2);
+  });
+
+  QUnit.test('parse can return null', function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      parse: function(attrs) {
+        attrs.value += 1;
+        return null;
+      }
+    });
+    var model = new Model({value: 1}, {parse: true});
+    assert.equal(JSON.stringify(model.toJSON()), '{}');
+  });
+
+  QUnit.test('url', function(assert) {
+    assert.expect(3);
+    doc.urlRoot = null;
+    assert.equal(doc.url(), '/collection/1-the-tempest');
+    doc.collection.url = '/collection/';
+    assert.equal(doc.url(), '/collection/1-the-tempest');
+    doc.collection = null;
+    assert.raises(function() { doc.url(); });
+    doc.collection = collection;
+  });
+
+  QUnit.test('url when using urlRoot, and uri encoding', function(assert) {
+    assert.expect(2);
+    var Model = Backbone.Model.extend({
+      urlRoot: '/collection'
+    });
+    var model = new Model();
+    assert.equal(model.url(), '/collection');
+    model.set({id: '+1+'});
+    assert.equal(model.url(), '/collection/%2B1%2B');
+  });
+
+  QUnit.test('url when using urlRoot as a function to determine urlRoot at runtime', function(assert) {
+    assert.expect(2);
+    var Model = Backbone.Model.extend({
+      urlRoot: function() {
+        return '/nested/' + this.get('parentId') + '/collection';
+      }
+    });
+
+    var model = new Model({parentId: 1});
+    assert.equal(model.url(), '/nested/1/collection');
+    model.set({id: 2});
+    assert.equal(model.url(), '/nested/1/collection/2');
+  });
+
+  QUnit.test('underscore methods', function(assert) {
+    assert.expect(5);
+    var model = new Backbone.Model({foo: 'a', bar: 'b', baz: 'c'});
+    var model2 = model.clone();
+    assert.deepEqual(model.keys(), ['foo', 'bar', 'baz']);
+    assert.deepEqual(model.values(), ['a', 'b', 'c']);
+    assert.deepEqual(model.invert(), {a: 'foo', b: 'bar', c: 'baz'});
+    assert.deepEqual(model.pick('foo', 'baz'), {foo: 'a', baz: 'c'});
+    assert.deepEqual(model.omit('foo', 'bar'), {baz: 'c'});
+  });
+
+  QUnit.test('chain', function(assert) {
+    var model = new Backbone.Model({a: 0, b: 1, c: 2});
+    assert.deepEqual(model.chain().pick('a', 'b', 'c').values().compact().value(), [1, 2]);
+  });
+
+  QUnit.test('clone', function(assert) {
+    assert.expect(10);
+    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
+    var b = a.clone();
+    assert.equal(a.get('foo'), 1);
+    assert.equal(a.get('bar'), 2);
+    assert.equal(a.get('baz'), 3);
+    assert.equal(b.get('foo'), a.get('foo'), 'Foo should be the same on the clone.');
+    assert.equal(b.get('bar'), a.get('bar'), 'Bar should be the same on the clone.');
+    assert.equal(b.get('baz'), a.get('baz'), 'Baz should be the same on the clone.');
+    a.set({foo: 100});
+    assert.equal(a.get('foo'), 100);
+    assert.equal(b.get('foo'), 1, 'Changing a parent attribute does not change the clone.');
+
+    var foo = new Backbone.Model({p: 1});
+    var bar = new Backbone.Model({p: 2});
+    bar.set(foo.clone().attributes, {unset: true});
+    assert.equal(foo.get('p'), 1);
+    assert.equal(bar.get('p'), undefined);
+  });
+
+  QUnit.test('isNew', function(assert) {
+    assert.expect(6);
+    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
+    assert.ok(a.isNew(), 'it should be new');
+    a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: -5});
+    assert.ok(!a.isNew(), 'any defined ID is legal, negative or positive');
+    a = new Backbone.Model({foo: 1, bar: 2, baz: 3, id: 0});
+    assert.ok(!a.isNew(), 'any defined ID is legal, including zero');
+    assert.ok(new Backbone.Model().isNew(), 'is true when there is no id');
+    assert.ok(!new Backbone.Model({id: 2}).isNew(), 'is false for a positive integer');
+    assert.ok(!new Backbone.Model({id: -5}).isNew(), 'is false for a negative integer');
+  });
+
+  QUnit.test('get', function(assert) {
+    assert.expect(2);
+    assert.equal(doc.get('title'), 'The Tempest');
+    assert.equal(doc.get('author'), 'Bill Shakespeare');
+  });
+
+  QUnit.test('escape', function(assert) {
+    assert.expect(5);
+    assert.equal(doc.escape('title'), 'The Tempest');
+    doc.set({audience: 'Bill & Bob'});
+    assert.equal(doc.escape('audience'), 'Bill &amp; Bob');
+    doc.set({audience: 'Tim > Joan'});
+    assert.equal(doc.escape('audience'), 'Tim &gt; Joan');
+    doc.set({audience: 10101});
+    assert.equal(doc.escape('audience'), '10101');
+    doc.unset('audience');
+    assert.equal(doc.escape('audience'), '');
+  });
+
+  QUnit.test('has', function(assert) {
+    assert.expect(10);
+    var model = new Backbone.Model();
+
+    assert.strictEqual(model.has('name'), false);
+
+    model.set({
+      '0': 0,
+      '1': 1,
+      'true': true,
+      'false': false,
+      'empty': '',
+      'name': 'name',
+      'null': null,
+      'undefined': undefined
+    });
+
+    assert.strictEqual(model.has('0'), true);
+    assert.strictEqual(model.has('1'), true);
+    assert.strictEqual(model.has('true'), true);
+    assert.strictEqual(model.has('false'), true);
+    assert.strictEqual(model.has('empty'), true);
+    assert.strictEqual(model.has('name'), true);
+
+    model.unset('name');
+
+    assert.strictEqual(model.has('name'), false);
+    assert.strictEqual(model.has('null'), false);
+    assert.strictEqual(model.has('undefined'), false);
+  });
+
+  QUnit.test('matches', function(assert) {
+    assert.expect(4);
+    var model = new Backbone.Model();
+
+    assert.strictEqual(model.matches({name: 'Jonas', cool: true}), false);
+
+    model.set({name: 'Jonas', cool: true});
+
+    assert.strictEqual(model.matches({name: 'Jonas'}), true);
+    assert.strictEqual(model.matches({name: 'Jonas', cool: true}), true);
+    assert.strictEqual(model.matches({name: 'Jonas', cool: false}), false);
+  });
+
+  QUnit.test('matches with predicate', function(assert) {
+    var model = new Backbone.Model({a: 0});
+
+    assert.strictEqual(model.matches(function(attr) {
+      return attr.a > 1 && attr.b != null;
+    }), false);
+
+    model.set({a: 3, b: true});
+
+    assert.strictEqual(model.matches(function(attr) {
+      return attr.a > 1 && attr.b != null;
+    }), true);
+  });
+
+  QUnit.test('set and unset', function(assert) {
+    assert.expect(8);
+    var a = new Backbone.Model({id: 'id', foo: 1, bar: 2, baz: 3});
+    var changeCount = 0;
+    a.on('change:foo', function() { changeCount += 1; });
+    a.set({foo: 2});
+    assert.equal(a.get('foo'), 2, 'Foo should have changed.');
+    assert.equal(changeCount, 1, 'Change count should have incremented.');
+    // set with value that is not new shouldn't fire change event
+    a.set({foo: 2});
+    assert.equal(a.get('foo'), 2, 'Foo should NOT have changed, still 2');
+    assert.equal(changeCount, 1, 'Change count should NOT have incremented.');
+
+    a.validate = function(attrs) {
+      assert.equal(attrs.foo, void 0, 'validate:true passed while unsetting');
+    };
+    a.unset('foo', {validate: true});
+    assert.equal(a.get('foo'), void 0, 'Foo should have changed');
+    delete a.validate;
+    assert.equal(changeCount, 2, 'Change count should have incremented for unset.');
+
+    a.unset('id');
+    assert.equal(a.id, undefined, 'Unsetting the id should remove the id property.');
+  });
+
+  QUnit.test('#2030 - set with failed validate, followed by another set triggers change', function(assert) {
+    var attr = 0, main = 0, error = 0;
+    var Model = Backbone.Model.extend({
+      validate: function(attrs) {
+        if (attrs.x > 1) {
+          error++;
+          return 'this is an error';
+        }
+      }
+    });
+    var model = new Model({x: 0});
+    model.on('change:x', function() { attr++; });
+    model.on('change', function() { main++; });
+    model.set({x: 2}, {validate: true});
+    model.set({x: 1}, {validate: true});
+    assert.deepEqual([attr, main, error], [1, 1, 1]);
+  });
+
+  QUnit.test('set triggers changes in the correct order', function(assert) {
+    var value = null;
+    var model = new Backbone.Model;
+    model.on('last', function(){ value = 'last'; });
+    model.on('first', function(){ value = 'first'; });
+    model.trigger('first');
+    model.trigger('last');
+    assert.equal(value, 'last');
+  });
+
+  QUnit.test('set falsy values in the correct order', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model({result: 'result'});
+    model.on('change', function() {
+      assert.equal(model.changed.result, void 0);
+      assert.equal(model.previous('result'), false);
+    });
+    model.set({result: void 0}, {silent: true});
+    model.set({result: null}, {silent: true});
+    model.set({result: false}, {silent: true});
+    model.set({result: void 0});
+  });
+
+  QUnit.test('nested set triggers with the correct options', function(assert) {
+    var model = new Backbone.Model();
+    var o1 = {};
+    var o2 = {};
+    var o3 = {};
+    model.on('change', function(__, options) {
+      switch (model.get('a')) {
+        case 1:
+          assert.equal(options, o1);
+          return model.set('a', 2, o2);
+        case 2:
+          assert.equal(options, o2);
+          return model.set('a', 3, o3);
+        case 3:
+          assert.equal(options, o3);
+      }
+    });
+    model.set('a', 1, o1);
+  });
+
+  QUnit.test('multiple unsets', function(assert) {
+    assert.expect(1);
+    var i = 0;
+    var counter = function(){ i++; };
+    var model = new Backbone.Model({a: 1});
+    model.on('change:a', counter);
+    model.set({a: 2});
+    model.unset('a');
+    model.unset('a');
+    assert.equal(i, 2, 'Unset does not fire an event for missing attributes.');
+  });
+
+  QUnit.test('unset and changedAttributes', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({a: 1});
+    model.on('change', function() {
+      assert.ok('a' in model.changedAttributes(), 'changedAttributes should contain unset properties');
+    });
+    model.unset('a');
+  });
+
+  QUnit.test('using a non-default id attribute.', function(assert) {
+    assert.expect(5);
+    var MongoModel = Backbone.Model.extend({idAttribute: '_id'});
+    var model = new MongoModel({id: 'eye-dee', _id: 25, title: 'Model'});
+    assert.equal(model.get('id'), 'eye-dee');
+    assert.equal(model.id, 25);
+    assert.equal(model.isNew(), false);
+    model.unset('_id');
+    assert.equal(model.id, undefined);
+    assert.equal(model.isNew(), true);
+  });
+
+  QUnit.test('setting an alternative cid prefix', function(assert) {
+    assert.expect(4);
+    var Model = Backbone.Model.extend({
+      cidPrefix: 'm'
+    });
+    var model = new Model();
+
+    assert.equal(model.cid.charAt(0), 'm');
+
+    model = new Backbone.Model();
+    assert.equal(model.cid.charAt(0), 'c');
+
+    var Collection = Backbone.Collection.extend({
+      model: Model
+    });
+    var col = new Collection([{id: 'c5'}, {id: 'c6'}, {id: 'c7'}]);
+
+    assert.equal(col.get('c6').cid.charAt(0), 'm');
+    col.set([{id: 'c6', value: 'test'}], {
+      merge: true,
+      add: true,
+      remove: false
+    });
+    assert.ok(col.get('c6').has('value'));
+  });
+
+  QUnit.test('set an empty string', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({name: 'Model'});
+    model.set({name: ''});
+    assert.equal(model.get('name'), '');
+  });
+
+  QUnit.test('setting an object', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({
+      custom: {foo: 1}
+    });
+    model.on('change', function() {
+      assert.ok(1);
+    });
+    model.set({
+      custom: {foo: 1} // no change should be fired
+    });
+    model.set({
+      custom: {foo: 2} // change event should be fired
+    });
+  });
+
+  QUnit.test('clear', function(assert) {
+    assert.expect(3);
+    var changed;
+    var model = new Backbone.Model({id: 1, name: 'Model'});
+    model.on('change:name', function(){ changed = true; });
+    model.on('change', function() {
+      var changedAttrs = model.changedAttributes();
+      assert.ok('name' in changedAttrs);
+    });
+    model.clear();
+    assert.equal(changed, true);
+    assert.equal(model.get('name'), undefined);
+  });
+
+  QUnit.test('defaults', function(assert) {
+    assert.expect(9);
+    var Defaulted = Backbone.Model.extend({
+      defaults: {
+        one: 1,
+        two: 2
+      }
+    });
+    var model = new Defaulted({two: undefined});
+    assert.equal(model.get('one'), 1);
+    assert.equal(model.get('two'), 2);
+    model = new Defaulted({two: 3});
+    assert.equal(model.get('one'), 1);
+    assert.equal(model.get('two'), 3);
+    Defaulted = Backbone.Model.extend({
+      defaults: function() {
+        return {
+          one: 3,
+          two: 4
+        };
+      }
+    });
+    model = new Defaulted({two: undefined});
+    assert.equal(model.get('one'), 3);
+    assert.equal(model.get('two'), 4);
+    Defaulted = Backbone.Model.extend({
+      defaults: {hasOwnProperty: true}
+    });
+    model = new Defaulted();
+    assert.equal(model.get('hasOwnProperty'), true);
+    model = new Defaulted({hasOwnProperty: undefined});
+    assert.equal(model.get('hasOwnProperty'), true);
+    model = new Defaulted({hasOwnProperty: false});
+    assert.equal(model.get('hasOwnProperty'), false);
+  });
+
+  QUnit.test('change, hasChanged, changedAttributes, previous, previousAttributes', function(assert) {
+    assert.expect(9);
+    var model = new Backbone.Model({name: 'Tim', age: 10});
+    assert.deepEqual(model.changedAttributes(), false);
+    model.on('change', function() {
+      assert.ok(model.hasChanged('name'), 'name changed');
+      assert.ok(!model.hasChanged('age'), 'age did not');
+      assert.ok(_.isEqual(model.changedAttributes(), {name: 'Rob'}), 'changedAttributes returns the changed attrs');
+      assert.equal(model.previous('name'), 'Tim');
+      assert.ok(_.isEqual(model.previousAttributes(), {name: 'Tim', age: 10}), 'previousAttributes is correct');
+    });
+    assert.equal(model.hasChanged(), false);
+    assert.equal(model.hasChanged(undefined), false);
+    model.set({name: 'Rob'});
+    assert.equal(model.get('name'), 'Rob');
+  });
+
+  QUnit.test('changedAttributes', function(assert) {
+    assert.expect(3);
+    var model = new Backbone.Model({a: 'a', b: 'b'});
+    assert.deepEqual(model.changedAttributes(), false);
+    assert.equal(model.changedAttributes({a: 'a'}), false);
+    assert.equal(model.changedAttributes({a: 'b'}).a, 'b');
+  });
+
+  QUnit.test('change with options', function(assert) {
+    assert.expect(2);
+    var value;
+    var model = new Backbone.Model({name: 'Rob'});
+    model.on('change', function(m, options) {
+      value = options.prefix + m.get('name');
+    });
+    model.set({name: 'Bob'}, {prefix: 'Mr. '});
+    assert.equal(value, 'Mr. Bob');
+    model.set({name: 'Sue'}, {prefix: 'Ms. '});
+    assert.equal(value, 'Ms. Sue');
+  });
+
+  QUnit.test('change after initialize', function(assert) {
+    assert.expect(1);
+    var changed = 0;
+    var attrs = {id: 1, label: 'c'};
+    var obj = new Backbone.Model(attrs);
+    obj.on('change', function() { changed += 1; });
+    obj.set(attrs);
+    assert.equal(changed, 0);
+  });
+
+  QUnit.test('save within change event', function(assert) {
+    assert.expect(1);
+    var env = this;
+    var model = new Backbone.Model({firstName: 'Taylor', lastName: 'Swift'});
+    model.url = '/test';
+    model.on('change', function() {
+      model.save();
+      assert.ok(_.isEqual(env.syncArgs.model, model));
+    });
+    model.set({lastName: 'Hicks'});
+  });
+
+  QUnit.test('validate after save', function(assert) {
+    assert.expect(2);
+    var lastError, model = new Backbone.Model();
+    model.validate = function(attrs) {
+      if (attrs.admin) return "Can't change admin status.";
+    };
+    model.sync = function(method, m, options) {
+      options.success.call(this, {admin: true});
+    };
+    model.on('invalid', function(m, error) {
+      lastError = error;
+    });
+    model.save(null);
+
+    assert.equal(lastError, "Can't change admin status.");
+    assert.equal(model.validationError, "Can't change admin status.");
+  });
+
+  QUnit.test('save', function(assert) {
+    assert.expect(2);
+    doc.save({title: 'Henry V'});
+    assert.equal(this.syncArgs.method, 'update');
+    assert.ok(_.isEqual(this.syncArgs.model, doc));
+  });
+
+  QUnit.test('save, fetch, destroy triggers error event when an error occurs', function(assert) {
+    assert.expect(3);
+    var model = new Backbone.Model();
+    model.on('error', function() {
+      assert.ok(true);
+    });
+    model.sync = function(method, m, options) {
+      options.error();
+    };
+    model.save({data: 2, id: 1});
+    model.fetch();
+    model.destroy();
+  });
+
+  QUnit.test('#3283 - save, fetch, destroy calls success with context', function(assert) {
+    assert.expect(3);
+    var model = new Backbone.Model();
+    var obj = {};
+    var options = {
+      context: obj,
+      success: function() {
+        assert.equal(this, obj);
+      }
+    };
+    model.sync = function(method, m, opts) {
+      opts.success.call(opts.context);
+    };
+    model.save({data: 2, id: 1}, options);
+    model.fetch(options);
+    model.destroy(options);
+  });
+
+  QUnit.test('#3283 - save, fetch, destroy calls error with context', function(assert) {
+    assert.expect(3);
+    var model = new Backbone.Model();
+    var obj = {};
+    var options = {
+      context: obj,
+      error: function() {
+        assert.equal(this, obj);
+      }
+    };
+    model.sync = function(method, m, opts) {
+      opts.error.call(opts.context);
+    };
+    model.save({data: 2, id: 1}, options);
+    model.fetch(options);
+    model.destroy(options);
+  });
+
+  QUnit.test('#3470 - save and fetch with parse false', function(assert) {
+    assert.expect(2);
+    var i = 0;
+    var model = new Backbone.Model();
+    model.parse = function() {
+      assert.ok(false);
+    };
+    model.sync = function(method, m, options) {
+      options.success({i: ++i});
+    };
+    model.fetch({parse: false});
+    assert.equal(model.get('i'), i);
+    model.save(null, {parse: false});
+    assert.equal(model.get('i'), i);
+  });
+
+  QUnit.test('save with PATCH', function(assert) {
+    doc.clear().set({id: 1, a: 1, b: 2, c: 3, d: 4});
+    doc.save();
+    assert.equal(this.syncArgs.method, 'update');
+    assert.equal(this.syncArgs.options.attrs, undefined);
+
+    doc.save({b: 2, d: 4}, {patch: true});
+    assert.equal(this.syncArgs.method, 'patch');
+    assert.equal(_.size(this.syncArgs.options.attrs), 2);
+    assert.equal(this.syncArgs.options.attrs.d, 4);
+    assert.equal(this.syncArgs.options.attrs.a, undefined);
+    assert.equal(this.ajaxSettings.data, '{"b":2,"d":4}');
+  });
+
+  QUnit.test('save with PATCH and different attrs', function(assert) {
+    doc.clear().save({b: 2, d: 4}, {patch: true, attrs: {B: 1, D: 3}});
+    assert.equal(this.syncArgs.options.attrs.D, 3);
+    assert.equal(this.syncArgs.options.attrs.d, undefined);
+    assert.equal(this.ajaxSettings.data, '{"B":1,"D":3}');
+    assert.deepEqual(doc.attributes, {b: 2, d: 4});
+  });
+
+  QUnit.test('save in positional style', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.sync = function(method, m, options) {
+      options.success();
+    };
+    model.save('title', 'Twelfth Night');
+    assert.equal(model.get('title'), 'Twelfth Night');
+  });
+
+  QUnit.test('save with non-object success response', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model();
+    model.sync = function(method, m, options) {
+      options.success('', options);
+      options.success(null, options);
+    };
+    model.save({testing: 'empty'}, {
+      success: function(m) {
+        assert.deepEqual(m.attributes, {testing: 'empty'});
+      }
+    });
+  });
+
+  QUnit.test('save with wait and supplied id', function(assert) {
+    var Model = Backbone.Model.extend({
+      urlRoot: '/collection'
+    });
+    var model = new Model();
+    model.save({id: 42}, {wait: true});
+    assert.equal(this.ajaxSettings.url, '/collection/42');
+  });
+
+  QUnit.test('save will pass extra options to success callback', function(assert) {
+    assert.expect(1);
+    var SpecialSyncModel = Backbone.Model.extend({
+      sync: function(method, m, options) {
+        _.extend(options, {specialSync: true});
+        return Backbone.Model.prototype.sync.call(this, method, m, options);
+      },
+      urlRoot: '/test'
+    });
+
+    var model = new SpecialSyncModel();
+
+    var onSuccess = function(m, response, options) {
+      assert.ok(options.specialSync, 'Options were passed correctly to callback');
+    };
+
+    model.save(null, {success: onSuccess});
+    this.ajaxSettings.success();
+  });
+
+  QUnit.test('fetch', function(assert) {
+    assert.expect(2);
+    doc.fetch();
+    assert.equal(this.syncArgs.method, 'read');
+    assert.ok(_.isEqual(this.syncArgs.model, doc));
+  });
+
+  QUnit.test('fetch will pass extra options to success callback', function(assert) {
+    assert.expect(1);
+    var SpecialSyncModel = Backbone.Model.extend({
+      sync: function(method, m, options) {
+        _.extend(options, {specialSync: true});
+        return Backbone.Model.prototype.sync.call(this, method, m, options);
+      },
+      urlRoot: '/test'
+    });
+
+    var model = new SpecialSyncModel();
+
+    var onSuccess = function(m, response, options) {
+      assert.ok(options.specialSync, 'Options were passed correctly to callback');
+    };
+
+    model.fetch({success: onSuccess});
+    this.ajaxSettings.success();
+  });
+
+  QUnit.test('destroy', function(assert) {
+    assert.expect(3);
+    doc.destroy();
+    assert.equal(this.syncArgs.method, 'delete');
+    assert.ok(_.isEqual(this.syncArgs.model, doc));
+
+    var newModel = new Backbone.Model;
+    assert.equal(newModel.destroy(), false);
+  });
+
+  QUnit.test('destroy will pass extra options to success callback', function(assert) {
+    assert.expect(1);
+    var SpecialSyncModel = Backbone.Model.extend({
+      sync: function(method, m, options) {
+        _.extend(options, {specialSync: true});
+        return Backbone.Model.prototype.sync.call(this, method, m, options);
+      },
+      urlRoot: '/test'
+    });
+
+    var model = new SpecialSyncModel({id: 'id'});
+
+    var onSuccess = function(m, response, options) {
+      assert.ok(options.specialSync, 'Options were passed correctly to callback');
+    };
+
+    model.destroy({success: onSuccess});
+    this.ajaxSettings.success();
+  });
+
+  QUnit.test('non-persisted destroy', function(assert) {
+    assert.expect(1);
+    var a = new Backbone.Model({foo: 1, bar: 2, baz: 3});
+    a.sync = function() { throw 'should not be called'; };
+    a.destroy();
+    assert.ok(true, 'non-persisted model should not call sync');
+  });
+
+  QUnit.test('validate', function(assert) {
+    var lastError;
+    var model = new Backbone.Model();
+    model.validate = function(attrs) {
+      if (attrs.admin !== this.get('admin')) return "Can't change admin status.";
+    };
+    model.on('invalid', function(m, error) {
+      lastError = error;
+    });
+    var result = model.set({a: 100});
+    assert.equal(result, model);
+    assert.equal(model.get('a'), 100);
+    assert.equal(lastError, undefined);
+    result = model.set({admin: true});
+    assert.equal(model.get('admin'), true);
+    result = model.set({a: 200, admin: false}, {validate: true});
+    assert.equal(lastError, "Can't change admin status.");
+    assert.equal(result, false);
+    assert.equal(model.get('a'), 100);
+  });
+
+  QUnit.test('validate on unset and clear', function(assert) {
+    assert.expect(6);
+    var error;
+    var model = new Backbone.Model({name: 'One'});
+    model.validate = function(attrs) {
+      if (!attrs.name) {
+        error = true;
+        return 'No thanks.';
+      }
+    };
+    model.set({name: 'Two'});
+    assert.equal(model.get('name'), 'Two');
+    assert.equal(error, undefined);
+    model.unset('name', {validate: true});
+    assert.equal(error, true);
+    assert.equal(model.get('name'), 'Two');
+    model.clear({validate: true});
+    assert.equal(model.get('name'), 'Two');
+    delete model.validate;
+    model.clear();
+    assert.equal(model.get('name'), undefined);
+  });
+
+  QUnit.test('validate with error callback', function(assert) {
+    assert.expect(8);
+    var lastError, boundError;
+    var model = new Backbone.Model();
+    model.validate = function(attrs) {
+      if (attrs.admin) return "Can't change admin status.";
+    };
+    model.on('invalid', function(m, error) {
+      boundError = true;
+    });
+    var result = model.set({a: 100}, {validate: true});
+    assert.equal(result, model);
+    assert.equal(model.get('a'), 100);
+    assert.equal(model.validationError, null);
+    assert.equal(boundError, undefined);
+    result = model.set({a: 200, admin: true}, {validate: true});
+    assert.equal(result, false);
+    assert.equal(model.get('a'), 100);
+    assert.equal(model.validationError, "Can't change admin status.");
+    assert.equal(boundError, true);
+  });
+
+  QUnit.test('defaults always extend attrs (#459)', function(assert) {
+    assert.expect(2);
+    var Defaulted = Backbone.Model.extend({
+      defaults: {one: 1},
+      initialize: function(attrs, opts) {
+        assert.equal(this.attributes.one, 1);
+      }
+    });
+    var providedattrs = new Defaulted({});
+    var emptyattrs = new Defaulted();
+  });
+
+  QUnit.test('Inherit class properties', function(assert) {
+    assert.expect(6);
+    var Parent = Backbone.Model.extend({
+      instancePropSame: function() {},
+      instancePropDiff: function() {}
+    }, {
+      classProp: function() {}
+    });
+    var Child = Parent.extend({
+      instancePropDiff: function() {}
+    });
+
+    var adult = new Parent;
+    var kid   = new Child;
+
+    assert.equal(Child.classProp, Parent.classProp);
+    assert.notEqual(Child.classProp, undefined);
+
+    assert.equal(kid.instancePropSame, adult.instancePropSame);
+    assert.notEqual(kid.instancePropSame, undefined);
+
+    assert.notEqual(Child.prototype.instancePropDiff, Parent.prototype.instancePropDiff);
+    assert.notEqual(Child.prototype.instancePropDiff, undefined);
+  });
+
+  QUnit.test("Nested change events don't clobber previous attributes", function(assert) {
+    assert.expect(4);
+    new Backbone.Model()
+    .on('change:state', function(m, newState) {
+      assert.equal(m.previous('state'), undefined);
+      assert.equal(newState, 'hello');
+      // Fire a nested change event.
+      m.set({other: 'whatever'});
+    })
+    .on('change:state', function(m, newState) {
+      assert.equal(m.previous('state'), undefined);
+      assert.equal(newState, 'hello');
+    })
+    .set({state: 'hello'});
+  });
+
+  QUnit.test('hasChanged/set should use same comparison', function(assert) {
+    assert.expect(2);
+    var changed = 0, model = new Backbone.Model({a: null});
+    model.on('change', function() {
+      assert.ok(this.hasChanged('a'));
+    })
+    .on('change:a', function() {
+      changed++;
+    })
+    .set({a: undefined});
+    assert.equal(changed, 1);
+  });
+
+  QUnit.test('#582, #425, change:attribute callbacks should fire after all changes have occurred', function(assert) {
+    assert.expect(9);
+    var model = new Backbone.Model;
+
+    var assertion = function() {
+      assert.equal(model.get('a'), 'a');
+      assert.equal(model.get('b'), 'b');
+      assert.equal(model.get('c'), 'c');
+    };
+
+    model.on('change:a', assertion);
+    model.on('change:b', assertion);
+    model.on('change:c', assertion);
+
+    model.set({a: 'a', b: 'b', c: 'c'});
+  });
+
+  QUnit.test('#871, set with attributes property', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.set({attributes: true});
+    assert.ok(model.has('attributes'));
+  });
+
+  QUnit.test('set value regardless of equality/change', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({x: []});
+    var a = [];
+    model.set({x: a});
+    assert.ok(model.get('x') === a);
+  });
+
+  QUnit.test('set same value does not trigger change', function(assert) {
+    assert.expect(0);
+    var model = new Backbone.Model({x: 1});
+    model.on('change change:x', function() { assert.ok(false); });
+    model.set({x: 1});
+    model.set({x: 1});
+  });
+
+  QUnit.test('unset does not fire a change for undefined attributes', function(assert) {
+    assert.expect(0);
+    var model = new Backbone.Model({x: undefined});
+    model.on('change:x', function(){ assert.ok(false); });
+    model.unset('x');
+  });
+
+  QUnit.test('set: undefined values', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({x: undefined});
+    assert.ok('x' in model.attributes);
+  });
+
+  QUnit.test('hasChanged works outside of change events, and true within', function(assert) {
+    assert.expect(6);
+    var model = new Backbone.Model({x: 1});
+    model.on('change:x', function() {
+      assert.ok(model.hasChanged('x'));
+      assert.equal(model.get('x'), 1);
+    });
+    model.set({x: 2}, {silent: true});
+    assert.ok(model.hasChanged());
+    assert.equal(model.hasChanged('x'), true);
+    model.set({x: 1});
+    assert.ok(model.hasChanged());
+    assert.equal(model.hasChanged('x'), true);
+  });
+
+  QUnit.test('hasChanged gets cleared on the following set', function(assert) {
+    assert.expect(4);
+    var model = new Backbone.Model;
+    model.set({x: 1});
+    assert.ok(model.hasChanged());
+    model.set({x: 1});
+    assert.ok(!model.hasChanged());
+    model.set({x: 2});
+    assert.ok(model.hasChanged());
+    model.set({});
+    assert.ok(!model.hasChanged());
+  });
+
+  QUnit.test('save with `wait` succeeds without `validate`', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.url = '/test';
+    model.save({x: 1}, {wait: true});
+    assert.ok(this.syncArgs.model === model);
+  });
+
+  QUnit.test("save without `wait` doesn't set invalid attributes", function(assert) {
+    var model = new Backbone.Model();
+    model.validate = function() { return 1; };
+    model.save({a: 1});
+    assert.equal(model.get('a'), void 0);
+  });
+
+  QUnit.test("save doesn't validate twice", function(assert) {
+    var model = new Backbone.Model();
+    var times = 0;
+    model.sync = function() {};
+    model.validate = function() { ++times; };
+    model.save({});
+    assert.equal(times, 1);
+  });
+
+  QUnit.test('`hasChanged` for falsey keys', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model();
+    model.set({x: true}, {silent: true});
+    assert.ok(!model.hasChanged(0));
+    assert.ok(!model.hasChanged(''));
+  });
+
+  QUnit.test('`previous` for falsey keys', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model({'0': true, '': true});
+    model.set({'0': false, '': false}, {silent: true});
+    assert.equal(model.previous(0), true);
+    assert.equal(model.previous(''), true);
+  });
+
+  QUnit.test('`save` with `wait` sends correct attributes', function(assert) {
+    assert.expect(5);
+    var changed = 0;
+    var model = new Backbone.Model({x: 1, y: 2});
+    model.url = '/test';
+    model.on('change:x', function() { changed++; });
+    model.save({x: 3}, {wait: true});
+    assert.deepEqual(JSON.parse(this.ajaxSettings.data), {x: 3, y: 2});
+    assert.equal(model.get('x'), 1);
+    assert.equal(changed, 0);
+    this.syncArgs.options.success({});
+    assert.equal(model.get('x'), 3);
+    assert.equal(changed, 1);
+  });
+
+  QUnit.test("a failed `save` with `wait` doesn't leave attributes behind", function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model;
+    model.url = '/test';
+    model.save({x: 1}, {wait: true});
+    assert.equal(model.get('x'), void 0);
+  });
+
+  QUnit.test('#1030 - `save` with `wait` results in correct attributes if success is called during sync', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model({x: 1, y: 2});
+    model.sync = function(method, m, options) {
+      options.success();
+    };
+    model.on('change:x', function() { assert.ok(true); });
+    model.save({x: 3}, {wait: true});
+    assert.equal(model.get('x'), 3);
+  });
+
+  QUnit.test('save with wait validates attributes', function(assert) {
+    var model = new Backbone.Model();
+    model.url = '/test';
+    model.validate = function() { assert.ok(true); };
+    model.save({x: 1}, {wait: true});
+  });
+
+  QUnit.test('save turns on parse flag', function(assert) {
+    var Model = Backbone.Model.extend({
+      sync: function(method, m, options) { assert.ok(options.parse); }
+    });
+    new Model().save();
+  });
+
+  QUnit.test("nested `set` during `'change:attr'`", function(assert) {
+    assert.expect(2);
+    var events = [];
+    var model = new Backbone.Model();
+    model.on('all', function(event) { events.push(event); });
+    model.on('change', function() {
+      model.set({z: true}, {silent: true});
+    });
+    model.on('change:x', function() {
+      model.set({y: true});
+    });
+    model.set({x: true});
+    assert.deepEqual(events, ['change:y', 'change:x', 'change']);
+    events = [];
+    model.set({z: true});
+    assert.deepEqual(events, []);
+  });
+
+  QUnit.test('nested `change` only fires once', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.on('change', function() {
+      assert.ok(true);
+      model.set({x: true});
+    });
+    model.set({x: true});
+  });
+
+  QUnit.test("nested `set` during `'change'`", function(assert) {
+    assert.expect(6);
+    var count = 0;
+    var model = new Backbone.Model();
+    model.on('change', function() {
+      switch (count++) {
+        case 0:
+          assert.deepEqual(this.changedAttributes(), {x: true});
+          assert.equal(model.previous('x'), undefined);
+          model.set({y: true});
+          break;
+        case 1:
+          assert.deepEqual(this.changedAttributes(), {x: true, y: true});
+          assert.equal(model.previous('x'), undefined);
+          model.set({z: true});
+          break;
+        case 2:
+          assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
+          assert.equal(model.previous('y'), undefined);
+          break;
+        default:
+          assert.ok(false);
+      }
+    });
+    model.set({x: true});
+  });
+
+  QUnit.test('nested `change` with silent', function(assert) {
+    assert.expect(3);
+    var count = 0;
+    var model = new Backbone.Model();
+    model.on('change:y', function() { assert.ok(false); });
+    model.on('change', function() {
+      switch (count++) {
+        case 0:
+          assert.deepEqual(this.changedAttributes(), {x: true});
+          model.set({y: true}, {silent: true});
+          model.set({z: true});
+          break;
+        case 1:
+          assert.deepEqual(this.changedAttributes(), {x: true, y: true, z: true});
+          break;
+        case 2:
+          assert.deepEqual(this.changedAttributes(), {z: false});
+          break;
+        default:
+          assert.ok(false);
+      }
+    });
+    model.set({x: true});
+    model.set({z: false});
+  });
+
+  QUnit.test('nested `change:attr` with silent', function(assert) {
+    assert.expect(0);
+    var model = new Backbone.Model();
+    model.on('change:y', function(){ assert.ok(false); });
+    model.on('change', function() {
+      model.set({y: true}, {silent: true});
+      model.set({z: true});
+    });
+    model.set({x: true});
+  });
+
+  QUnit.test('multiple nested changes with silent', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.on('change:x', function() {
+      model.set({y: 1}, {silent: true});
+      model.set({y: 2});
+    });
+    model.on('change:y', function(m, val) {
+      assert.equal(val, 2);
+    });
+    model.set({x: true});
+  });
+
+  QUnit.test('multiple nested changes with silent', function(assert) {
+    assert.expect(1);
+    var changes = [];
+    var model = new Backbone.Model();
+    model.on('change:b', function(m, val) { changes.push(val); });
+    model.on('change', function() {
+      model.set({b: 1});
+    });
+    model.set({b: 0});
+    assert.deepEqual(changes, [0, 1]);
+  });
+
+  QUnit.test('basic silent change semantics', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model;
+    model.set({x: 1});
+    model.on('change', function(){ assert.ok(true); });
+    model.set({x: 2}, {silent: true});
+    model.set({x: 1});
+  });
+
+  QUnit.test('nested set multiple times', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.on('change:b', function() {
+      assert.ok(true);
+    });
+    model.on('change:a', function() {
+      model.set({b: true});
+      model.set({b: true});
+    });
+    model.set({a: true});
+  });
+
+  QUnit.test('#1122 - clear does not alter options.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    var options = {};
+    model.clear(options);
+    assert.ok(!options.unset);
+  });
+
+  QUnit.test('#1122 - unset does not alter options.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    var options = {};
+    model.unset('x', options);
+    assert.ok(!options.unset);
+  });
+
+  QUnit.test('#1355 - `options` is passed to success callbacks', function(assert) {
+    assert.expect(3);
+    var model = new Backbone.Model();
+    var opts = {
+      success: function( m, resp, options ) {
+        assert.ok(options);
+      }
+    };
+    model.sync = function(method, m, options) {
+      options.success();
+    };
+    model.save({id: 1}, opts);
+    model.fetch(opts);
+    model.destroy(opts);
+  });
+
+  QUnit.test("#1412 - Trigger 'sync' event.", function(assert) {
+    assert.expect(3);
+    var model = new Backbone.Model({id: 1});
+    model.sync = function(method, m, options) { options.success(); };
+    model.on('sync', function(){ assert.ok(true); });
+    model.fetch();
+    model.save();
+    model.destroy();
+  });
+
+  QUnit.test('#1365 - Destroy: New models execute success callback.', function(assert) {
+    var done = assert.async();
+    assert.expect(2);
+    new Backbone.Model()
+    .on('sync', function() { assert.ok(false); })
+    .on('destroy', function(){ assert.ok(true); })
+    .destroy({success: function(){
+      assert.ok(true);
+      done();
+    }});
+  });
+
+  QUnit.test('#1433 - Save: An invalid model cannot be persisted.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model;
+    model.validate = function(){ return 'invalid'; };
+    model.sync = function(){ assert.ok(false); };
+    assert.strictEqual(model.save(), false);
+  });
+
+  QUnit.test("#1377 - Save without attrs triggers 'error'.", function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      url: '/test/',
+      sync: function(method, m, options){ options.success(); },
+      validate: function(){ return 'invalid'; }
+    });
+    var model = new Model({id: 1});
+    model.on('invalid', function(){ assert.ok(true); });
+    model.save();
+  });
+
+  QUnit.test('#1545 - `undefined` can be passed to a model constructor without coersion', function(assert) {
+    var Model = Backbone.Model.extend({
+      defaults: {one: 1},
+      initialize: function(attrs, opts) {
+        assert.equal(attrs, undefined);
+      }
+    });
+    var emptyattrs = new Model();
+    var undefinedattrs = new Model(undefined);
+  });
+
+  QUnit.test('#1478 - Model `save` does not trigger change on unchanged attributes', function(assert) {
+    var done = assert.async();
+    assert.expect(0);
+    var Model = Backbone.Model.extend({
+      sync: function(method, m, options) {
+        setTimeout(function(){
+          options.success();
+          done();
+        }, 0);
+      }
+    });
+    new Model({x: true})
+    .on('change:x', function(){ assert.ok(false); })
+    .save(null, {wait: true});
+  });
+
+  QUnit.test('#1664 - Changing from one value, silently to another, back to original triggers a change.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model({x: 1});
+    model.on('change:x', function() { assert.ok(true); });
+    model.set({x: 2}, {silent: true});
+    model.set({x: 3}, {silent: true});
+    model.set({x: 1});
+  });
+
+  QUnit.test('#1664 - multiple silent changes nested inside a change event', function(assert) {
+    assert.expect(2);
+    var changes = [];
+    var model = new Backbone.Model();
+    model.on('change', function() {
+      model.set({a: 'c'}, {silent: true});
+      model.set({b: 2}, {silent: true});
+      model.unset('c', {silent: true});
+    });
+    model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
+    model.set({a: 'a', b: 1, c: 'item'});
+    assert.deepEqual(changes, ['a', 1, 'item']);
+    assert.deepEqual(model.attributes, {a: 'c', b: 2});
+  });
+
+  QUnit.test('#1791 - `attributes` is available for `parse`', function(assert) {
+    var Model = Backbone.Model.extend({
+      parse: function() { this.has('a'); } // shouldn't throw an error
+    });
+    var model = new Model(null, {parse: true});
+    assert.expect(0);
+  });
+
+  QUnit.test('silent changes in last `change` event back to original triggers change', function(assert) {
+    assert.expect(2);
+    var changes = [];
+    var model = new Backbone.Model();
+    model.on('change:a change:b change:c', function(m, val) { changes.push(val); });
+    model.on('change', function() {
+      model.set({a: 'c'}, {silent: true});
+    });
+    model.set({a: 'a'});
+    assert.deepEqual(changes, ['a']);
+    model.set({a: 'a'});
+    assert.deepEqual(changes, ['a', 'a']);
+  });
+
+  QUnit.test('#1943 change calculations should use _.isEqual', function(assert) {
+    var model = new Backbone.Model({a: {key: 'value'}});
+    model.set('a', {key: 'value'}, {silent: true});
+    assert.equal(model.changedAttributes(), false);
+  });
+
+  QUnit.test('#1964 - final `change` event is always fired, regardless of interim changes', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.on('change:property', function() {
+      model.set('property', 'bar');
+    });
+    model.on('change', function() {
+      assert.ok(true);
+    });
+    model.set('property', 'foo');
+  });
+
+  QUnit.test('isValid', function(assert) {
+    var model = new Backbone.Model({valid: true});
+    model.validate = function(attrs) {
+      if (!attrs.valid) return 'invalid';
+    };
+    assert.equal(model.isValid(), true);
+    assert.equal(model.set({valid: false}, {validate: true}), false);
+    assert.equal(model.isValid(), true);
+    model.set({valid: false});
+    assert.equal(model.isValid(), false);
+    assert.ok(!model.set('valid', false, {validate: true}));
+  });
+
+  QUnit.test('#1179 - isValid returns true in the absence of validate.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.validate = null;
+    assert.ok(model.isValid());
+  });
+
+  QUnit.test('#1961 - Creating a model with {validate:true} will call validate and use the error callback', function(assert) {
+    var Model = Backbone.Model.extend({
+      validate: function(attrs) {
+        if (attrs.id === 1) return "This shouldn't happen";
+      }
+    });
+    var model = new Model({id: 1}, {validate: true});
+    assert.equal(model.validationError, "This shouldn't happen");
+  });
+
+  QUnit.test('toJSON receives attrs during save(..., {wait: true})', function(assert) {
+    assert.expect(1);
+    var Model = Backbone.Model.extend({
+      url: '/test',
+      toJSON: function() {
+        assert.strictEqual(this.attributes.x, 1);
+        return _.clone(this.attributes);
+      }
+    });
+    var model = new Model;
+    model.save({x: 1}, {wait: true});
+  });
+
+  QUnit.test('#2034 - nested set with silent only triggers one change', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model();
+    model.on('change', function() {
+      model.set({b: true}, {silent: true});
+      assert.ok(true);
+    });
+    model.set({a: true});
+  });
+
+  QUnit.test('#3778 - id will only be updated if it is set', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model({id: 1});
+    model.id = 2;
+    model.set({foo: 'bar'});
+    assert.equal(model.id, 2);
+    model.set({id: 3});
+    assert.equal(model.id, 3);
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/noconflict.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/noconflict.js
new file mode 100644
index 0000000..9968f68
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/noconflict.js
@@ -0,0 +1,13 @@
+(function() {
+
+  QUnit.module('Backbone.noConflict');
+
+  QUnit.test('noConflict', function(assert) {
+    assert.expect(2);
+    var noconflictBackbone = Backbone.noConflict();
+    assert.equal(window.Backbone, undefined, 'Returned window.Backbone');
+    window.Backbone = noconflictBackbone;
+    assert.equal(window.Backbone, noconflictBackbone, 'Backbone is still pointing to the original Backbone');
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/router.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/router.js
new file mode 100644
index 0000000..13110c4
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/router.js
@@ -0,0 +1,1062 @@
+(function() {
+
+  var router = null;
+  var location = null;
+  var lastRoute = null;
+  var lastArgs = [];
+
+  var onRoute = function(routerParam, route, args) {
+    lastRoute = route;
+    lastArgs = args;
+  };
+
+  var Location = function(href) {
+    this.replace(href);
+  };
+
+  _.extend(Location.prototype, {
+
+    parser: document.createElement('a'),
+
+    replace: function(href) {
+      this.parser.href = href;
+      _.extend(this, _.pick(this.parser,
+        'href',
+        'hash',
+        'host',
+        'search',
+        'fragment',
+        'pathname',
+        'protocol'
+      ));
+      // In IE, anchor.pathname does not contain a leading slash though
+      // window.location.pathname does.
+      if (!/^\//.test(this.pathname)) this.pathname = '/' + this.pathname;
+    },
+
+    toString: function() {
+      return this.href;
+    }
+
+  });
+
+  QUnit.module('Backbone.Router', {
+
+    setup: function() {
+      location = new Location('http://example.com');
+      Backbone.history = _.extend(new Backbone.History, {location: location});
+      router = new Router({testing: 101});
+      Backbone.history.interval = 9;
+      Backbone.history.start({pushState: false});
+      lastRoute = null;
+      lastArgs = [];
+      Backbone.history.on('route', onRoute);
+    },
+
+    teardown: function() {
+      Backbone.history.stop();
+      Backbone.history.off('route', onRoute);
+    }
+
+  });
+
+  var ExternalObject = {
+    value: 'unset',
+
+    routingFunction: function(value) {
+      this.value = value;
+    }
+  };
+  ExternalObject.routingFunction = _.bind(ExternalObject.routingFunction, ExternalObject);
+
+  var Router = Backbone.Router.extend({
+
+    count: 0,
+
+    routes: {
+      'noCallback': 'noCallback',
+      'counter': 'counter',
+      'search/:query': 'search',
+      'search/:query/p:page': 'search',
+      'charñ': 'charUTF',
+      'char%C3%B1': 'charEscaped',
+      'contacts': 'contacts',
+      'contacts/new': 'newContact',
+      'contacts/:id': 'loadContact',
+      'route-event/:arg': 'routeEvent',
+      'optional(/:item)': 'optionalItem',
+      'named/optional/(y:z)': 'namedOptional',
+      'splat/*args/end': 'splat',
+      ':repo/compare/*from...*to': 'github',
+      'decode/:named/*splat': 'decode',
+      '*first/complex-*part/*rest': 'complex',
+      'query/:entity': 'query',
+      'function/:value': ExternalObject.routingFunction,
+      '*anything': 'anything'
+    },
+
+    initialize: function(options) {
+      this.testing = options.testing;
+      this.route('implicit', 'implicit');
+    },
+
+    counter: function() {
+      this.count++;
+    },
+
+    implicit: function() {
+      this.count++;
+    },
+
+    search: function(query, page) {
+      this.query = query;
+      this.page = page;
+    },
+
+    charUTF: function() {
+      this.charType = 'UTF';
+    },
+
+    charEscaped: function() {
+      this.charType = 'escaped';
+    },
+
+    contacts: function(){
+      this.contact = 'index';
+    },
+
+    newContact: function(){
+      this.contact = 'new';
+    },
+
+    loadContact: function(){
+      this.contact = 'load';
+    },
+
+    optionalItem: function(arg){
+      this.arg = arg !== void 0 ? arg : null;
+    },
+
+    splat: function(args) {
+      this.args = args;
+    },
+
+    github: function(repo, from, to) {
+      this.repo = repo;
+      this.from = from;
+      this.to = to;
+    },
+
+    complex: function(first, part, rest) {
+      this.first = first;
+      this.part = part;
+      this.rest = rest;
+    },
+
+    query: function(entity, args) {
+      this.entity    = entity;
+      this.queryArgs = args;
+    },
+
+    anything: function(whatever) {
+      this.anything = whatever;
+    },
+
+    namedOptional: function(z) {
+      this.z = z;
+    },
+
+    decode: function(named, path) {
+      this.named = named;
+      this.path = path;
+    },
+
+    routeEvent: function(arg) {
+    }
+
+  });
+
+  QUnit.test('initialize', function(assert) {
+    assert.expect(1);
+    assert.equal(router.testing, 101);
+  });
+
+  QUnit.test('routes (simple)', function(assert) {
+    assert.expect(4);
+    location.replace('http://example.com#search/news');
+    Backbone.history.checkUrl();
+    assert.equal(router.query, 'news');
+    assert.equal(router.page, void 0);
+    assert.equal(lastRoute, 'search');
+    assert.equal(lastArgs[0], 'news');
+  });
+
+  QUnit.test('routes (simple, but unicode)', function(assert) {
+    assert.expect(4);
+    location.replace('http://example.com#search/тест');
+    Backbone.history.checkUrl();
+    assert.equal(router.query, 'тест');
+    assert.equal(router.page, void 0);
+    assert.equal(lastRoute, 'search');
+    assert.equal(lastArgs[0], 'тест');
+  });
+
+  QUnit.test('routes (two part)', function(assert) {
+    assert.expect(2);
+    location.replace('http://example.com#search/nyc/p10');
+    Backbone.history.checkUrl();
+    assert.equal(router.query, 'nyc');
+    assert.equal(router.page, '10');
+  });
+
+  QUnit.test('routes via navigate', function(assert) {
+    assert.expect(2);
+    Backbone.history.navigate('search/manhattan/p20', {trigger: true});
+    assert.equal(router.query, 'manhattan');
+    assert.equal(router.page, '20');
+  });
+
+  QUnit.test('routes via navigate with params', function(assert) {
+    assert.expect(1);
+    Backbone.history.navigate('query/test?a=b', {trigger: true});
+    assert.equal(router.queryArgs, 'a=b');
+  });
+
+  QUnit.test('routes via navigate for backwards-compatibility', function(assert) {
+    assert.expect(2);
+    Backbone.history.navigate('search/manhattan/p20', true);
+    assert.equal(router.query, 'manhattan');
+    assert.equal(router.page, '20');
+  });
+
+  QUnit.test('reports matched route via nagivate', function(assert) {
+    assert.expect(1);
+    assert.ok(Backbone.history.navigate('search/manhattan/p20', true));
+  });
+
+  QUnit.test('route precedence via navigate', function(assert){
+    assert.expect(6);
+    // check both 0.9.x and backwards-compatibility options
+    _.each([{trigger: true}, true], function( options ){
+      Backbone.history.navigate('contacts', options);
+      assert.equal(router.contact, 'index');
+      Backbone.history.navigate('contacts/new', options);
+      assert.equal(router.contact, 'new');
+      Backbone.history.navigate('contacts/foo', options);
+      assert.equal(router.contact, 'load');
+    });
+  });
+
+  QUnit.test('loadUrl is not called for identical routes.', function(assert) {
+    assert.expect(0);
+    Backbone.history.loadUrl = function(){ assert.ok(false); };
+    location.replace('http://example.com#route');
+    Backbone.history.navigate('route');
+    Backbone.history.navigate('/route');
+    Backbone.history.navigate('/route');
+  });
+
+  QUnit.test('use implicit callback if none provided', function(assert) {
+    assert.expect(1);
+    router.count = 0;
+    router.navigate('implicit', {trigger: true});
+    assert.equal(router.count, 1);
+  });
+
+  QUnit.test('routes via navigate with {replace: true}', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com#start_here');
+    Backbone.history.checkUrl();
+    location.replace = function(href) {
+      assert.strictEqual(href, new Location('http://example.com#end_here').href);
+    };
+    Backbone.history.navigate('end_here', {replace: true});
+  });
+
+  QUnit.test('routes (splats)', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com#splat/long-list/of/splatted_99args/end');
+    Backbone.history.checkUrl();
+    assert.equal(router.args, 'long-list/of/splatted_99args');
+  });
+
+  QUnit.test('routes (github)', function(assert) {
+    assert.expect(3);
+    location.replace('http://example.com#backbone/compare/1.0...braddunbar:with/slash');
+    Backbone.history.checkUrl();
+    assert.equal(router.repo, 'backbone');
+    assert.equal(router.from, '1.0');
+    assert.equal(router.to, 'braddunbar:with/slash');
+  });
+
+  QUnit.test('routes (optional)', function(assert) {
+    assert.expect(2);
+    location.replace('http://example.com#optional');
+    Backbone.history.checkUrl();
+    assert.ok(!router.arg);
+    location.replace('http://example.com#optional/thing');
+    Backbone.history.checkUrl();
+    assert.equal(router.arg, 'thing');
+  });
+
+  QUnit.test('routes (complex)', function(assert) {
+    assert.expect(3);
+    location.replace('http://example.com#one/two/three/complex-part/four/five/six/seven');
+    Backbone.history.checkUrl();
+    assert.equal(router.first, 'one/two/three');
+    assert.equal(router.part, 'part');
+    assert.equal(router.rest, 'four/five/six/seven');
+  });
+
+  QUnit.test('routes (query)', function(assert) {
+    assert.expect(5);
+    location.replace('http://example.com#query/mandel?a=b&c=d');
+    Backbone.history.checkUrl();
+    assert.equal(router.entity, 'mandel');
+    assert.equal(router.queryArgs, 'a=b&c=d');
+    assert.equal(lastRoute, 'query');
+    assert.equal(lastArgs[0], 'mandel');
+    assert.equal(lastArgs[1], 'a=b&c=d');
+  });
+
+  QUnit.test('routes (anything)', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com#doesnt-match-a-route');
+    Backbone.history.checkUrl();
+    assert.equal(router.anything, 'doesnt-match-a-route');
+  });
+
+  QUnit.test('routes (function)', function(assert) {
+    assert.expect(3);
+    router.on('route', function(name) {
+      assert.ok(name === '');
+    });
+    assert.equal(ExternalObject.value, 'unset');
+    location.replace('http://example.com#function/set');
+    Backbone.history.checkUrl();
+    assert.equal(ExternalObject.value, 'set');
+  });
+
+  QUnit.test('Decode named parameters, not splats.', function(assert) {
+    assert.expect(2);
+    location.replace('http://example.com#decode/a%2Fb/c%2Fd/e');
+    Backbone.history.checkUrl();
+    assert.strictEqual(router.named, 'a/b');
+    assert.strictEqual(router.path, 'c/d/e');
+  });
+
+  QUnit.test("fires event when router doesn't have callback on it", function(assert) {
+    assert.expect(1);
+    router.on('route:noCallback', function(){ assert.ok(true); });
+    location.replace('http://example.com#noCallback');
+    Backbone.history.checkUrl();
+  });
+
+  QUnit.test('No events are triggered if #execute returns false.', function(assert) {
+    assert.expect(1);
+    var MyRouter = Backbone.Router.extend({
+
+      routes: {
+        foo: function() {
+          assert.ok(true);
+        }
+      },
+
+      execute: function(callback, args) {
+        callback.apply(this, args);
+        return false;
+      }
+
+    });
+
+    var myRouter = new MyRouter;
+
+    myRouter.on('route route:foo', function() {
+      assert.ok(false);
+    });
+
+    Backbone.history.on('route', function() {
+      assert.ok(false);
+    });
+
+    location.replace('http://example.com#foo');
+    Backbone.history.checkUrl();
+  });
+
+  QUnit.test('#933, #908 - leading slash', function(assert) {
+    assert.expect(2);
+    location.replace('http://example.com/root/foo');
+
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({root: '/root', hashChange: false, silent: true});
+    assert.strictEqual(Backbone.history.getFragment(), 'foo');
+
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({root: '/root/', hashChange: false, silent: true});
+    assert.strictEqual(Backbone.history.getFragment(), 'foo');
+  });
+
+  QUnit.test('#967 - Route callback gets passed encoded values.', function(assert) {
+    assert.expect(3);
+    var route = 'has%2Fslash/complex-has%23hash/has%20space';
+    Backbone.history.navigate(route, {trigger: true});
+    assert.strictEqual(router.first, 'has/slash');
+    assert.strictEqual(router.part, 'has#hash');
+    assert.strictEqual(router.rest, 'has space');
+  });
+
+  QUnit.test('correctly handles URLs with % (#868)', function(assert) {
+    assert.expect(3);
+    location.replace('http://example.com#search/fat%3A1.5%25');
+    Backbone.history.checkUrl();
+    location.replace('http://example.com#search/fat');
+    Backbone.history.checkUrl();
+    assert.equal(router.query, 'fat');
+    assert.equal(router.page, void 0);
+    assert.equal(lastRoute, 'search');
+  });
+
+  QUnit.test('#2666 - Hashes with UTF8 in them.', function(assert) {
+    assert.expect(2);
+    Backbone.history.navigate('charñ', {trigger: true});
+    assert.equal(router.charType, 'UTF');
+    Backbone.history.navigate('char%C3%B1', {trigger: true});
+    assert.equal(router.charType, 'UTF');
+  });
+
+  QUnit.test('#1185 - Use pathname when hashChange is not wanted.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/path/name#hash');
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({hashChange: false});
+    var fragment = Backbone.history.getFragment();
+    assert.strictEqual(fragment, location.pathname.replace(/^\//, ''));
+  });
+
+  QUnit.test('#1206 - Strip leading slash before location.assign.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root/');
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({hashChange: false, root: '/root/'});
+    location.assign = function(pathname) {
+      assert.strictEqual(pathname, '/root/fragment');
+    };
+    Backbone.history.navigate('/fragment');
+  });
+
+  QUnit.test('#1387 - Root fragment without trailing slash.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root');
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({hashChange: false, root: '/root/', silent: true});
+    assert.strictEqual(Backbone.history.getFragment(), '');
+  });
+
+  QUnit.test('#1366 - History does not prepend root to fragment.', function(assert) {
+    assert.expect(2);
+    Backbone.history.stop();
+    location.replace('http://example.com/root/');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url) {
+          assert.strictEqual(url, '/root/x');
+        }
+      }
+    });
+    Backbone.history.start({
+      root: '/root/',
+      pushState: true,
+      hashChange: false
+    });
+    Backbone.history.navigate('x');
+    assert.strictEqual(Backbone.history.fragment, 'x');
+  });
+
+  QUnit.test('Normalize root.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url) {
+          assert.strictEqual(url, '/root/fragment');
+        }
+      }
+    });
+    Backbone.history.start({
+      pushState: true,
+      root: '/root',
+      hashChange: false
+    });
+    Backbone.history.navigate('fragment');
+  });
+
+  QUnit.test('Normalize root.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root#fragment');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url) {},
+        replaceState: function(state, title, url) {
+          assert.strictEqual(url, '/root/fragment');
+        }
+      }
+    });
+    Backbone.history.start({
+      pushState: true,
+      root: '/root'
+    });
+  });
+
+  QUnit.test('Normalize root.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root');
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.loadUrl = function() { assert.ok(true); };
+    Backbone.history.start({
+      pushState: true,
+      root: '/root'
+    });
+  });
+
+  QUnit.test('Normalize root - leading slash.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){},
+        replaceState: function(){}
+      }
+    });
+    Backbone.history.start({root: 'root'});
+    assert.strictEqual(Backbone.history.root, '/root/');
+  });
+
+  QUnit.test('Transition from hashChange to pushState.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root#x/y');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){},
+        replaceState: function(state, title, url){
+          assert.strictEqual(url, '/root/x/y');
+        }
+      }
+    });
+    Backbone.history.start({
+      root: 'root',
+      pushState: true
+    });
+  });
+
+  QUnit.test('#1619: Router: Normalize empty root', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){},
+        replaceState: function(){}
+      }
+    });
+    Backbone.history.start({root: ''});
+    assert.strictEqual(Backbone.history.root, '/');
+  });
+
+  QUnit.test('#1619: Router: nagivate with empty root', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url) {
+          assert.strictEqual(url, '/fragment');
+        }
+      }
+    });
+    Backbone.history.start({
+      pushState: true,
+      root: '',
+      hashChange: false
+    });
+    Backbone.history.navigate('fragment');
+  });
+
+  QUnit.test('Transition from pushState to hashChange.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root/x/y?a=b');
+    location.replace = function(url) {
+      assert.strictEqual(url, '/root#x/y?a=b');
+    };
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: null,
+        replaceState: null
+      }
+    });
+    Backbone.history.start({
+      root: 'root',
+      pushState: true
+    });
+  });
+
+  QUnit.test('#1695 - hashChange to pushState with search.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root#x/y?a=b');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){},
+        replaceState: function(state, title, url){
+          assert.strictEqual(url, '/root/x/y?a=b');
+        }
+      }
+    });
+    Backbone.history.start({
+      root: 'root',
+      pushState: true
+    });
+  });
+
+  QUnit.test('#1746 - Router allows empty route.', function(assert) {
+    assert.expect(1);
+    var MyRouter = Backbone.Router.extend({
+      routes: {'': 'empty'},
+      empty: function(){},
+      route: function(route){
+        assert.strictEqual(route, '');
+      }
+    });
+    new MyRouter;
+  });
+
+  QUnit.test('#1794 - Trailing space in fragments.', function(assert) {
+    assert.expect(1);
+    var history = new Backbone.History;
+    assert.strictEqual(history.getFragment('fragment   '), 'fragment');
+  });
+
+  QUnit.test('#1820 - Leading slash and trailing space.', 1, function(assert) {
+    var history = new Backbone.History;
+    assert.strictEqual(history.getFragment('/fragment '), 'fragment');
+  });
+
+  QUnit.test('#1980 - Optional parameters.', function(assert) {
+    assert.expect(2);
+    location.replace('http://example.com#named/optional/y');
+    Backbone.history.checkUrl();
+    assert.strictEqual(router.z, undefined);
+    location.replace('http://example.com#named/optional/y123');
+    Backbone.history.checkUrl();
+    assert.strictEqual(router.z, '123');
+  });
+
+  QUnit.test("#2062 - Trigger 'route' event on router instance.", function(assert) {
+    assert.expect(2);
+    router.on('route', function(name, args) {
+      assert.strictEqual(name, 'routeEvent');
+      assert.deepEqual(args, ['x', null]);
+    });
+    location.replace('http://example.com#route-event/x');
+    Backbone.history.checkUrl();
+  });
+
+  QUnit.test('#2255 - Extend routes by making routes a function.', function(assert) {
+    assert.expect(1);
+    var RouterBase = Backbone.Router.extend({
+      routes: function() {
+        return {
+          home: 'root',
+          index: 'index.html'
+        };
+      }
+    });
+
+    var RouterExtended = RouterBase.extend({
+      routes: function() {
+        var _super = RouterExtended.__super__.routes;
+        return _.extend(_super(), {show: 'show', search: 'search'});
+      }
+    });
+
+    var myRouter = new RouterExtended();
+    assert.deepEqual({home: 'root', index: 'index.html', show: 'show', search: 'search'}, myRouter.routes);
+  });
+
+  QUnit.test('#2538 - hashChange to pushState only if both requested.', function(assert) {
+    assert.expect(0);
+    Backbone.history.stop();
+    location.replace('http://example.com/root?a=b#x/y');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){},
+        replaceState: function(){ assert.ok(false); }
+      }
+    });
+    Backbone.history.start({
+      root: 'root',
+      pushState: true,
+      hashChange: false
+    });
+  });
+
+  QUnit.test('No hash fallback.', function(assert) {
+    assert.expect(0);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){},
+        replaceState: function(){}
+      }
+    });
+
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        hash: function() { assert.ok(false); }
+      }
+    });
+    var myRouter = new MyRouter;
+
+    location.replace('http://example.com/');
+    Backbone.history.start({
+      pushState: true,
+      hashChange: false
+    });
+    location.replace('http://example.com/nomatch#hash');
+    Backbone.history.checkUrl();
+  });
+
+  QUnit.test('#2656 - No trailing slash on root.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url){
+          assert.strictEqual(url, '/root');
+        }
+      }
+    });
+    location.replace('http://example.com/root/path');
+    Backbone.history.start({pushState: true, hashChange: false, root: 'root'});
+    Backbone.history.navigate('');
+  });
+
+  QUnit.test('#2656 - No trailing slash on root.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url) {
+          assert.strictEqual(url, '/');
+        }
+      }
+    });
+    location.replace('http://example.com/path');
+    Backbone.history.start({pushState: true, hashChange: false});
+    Backbone.history.navigate('');
+  });
+
+  QUnit.test('#2656 - No trailing slash on root.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url){
+          assert.strictEqual(url, '/root?x=1');
+        }
+      }
+    });
+    location.replace('http://example.com/root/path');
+    Backbone.history.start({pushState: true, hashChange: false, root: 'root'});
+    Backbone.history.navigate('?x=1');
+  });
+
+  QUnit.test('#2765 - Fragment matching sans query/hash.', function(assert) {
+    assert.expect(2);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(state, title, url) {
+          assert.strictEqual(url, '/path?query#hash');
+        }
+      }
+    });
+
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        path: function() { assert.ok(true); }
+      }
+    });
+    var myRouter = new MyRouter;
+
+    location.replace('http://example.com/');
+    Backbone.history.start({pushState: true, hashChange: false});
+    Backbone.history.navigate('path?query#hash', true);
+  });
+
+  QUnit.test('Do not decode the search params.', function(assert) {
+    assert.expect(1);
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        path: function(params){
+          assert.strictEqual(params, 'x=y%3Fz');
+        }
+      }
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.navigate('path?x=y%3Fz', true);
+  });
+
+  QUnit.test('Navigate to a hash url.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({pushState: true});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        path: function(params) {
+          assert.strictEqual(params, 'x=y');
+        }
+      }
+    });
+    var myRouter = new MyRouter;
+    location.replace('http://example.com/path?x=y#hash');
+    Backbone.history.checkUrl();
+  });
+
+  QUnit.test('#navigate to a hash url.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    Backbone.history.start({pushState: true});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        path: function(params) {
+          assert.strictEqual(params, 'x=y');
+        }
+      }
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.navigate('path?x=y#hash', true);
+  });
+
+  QUnit.test('unicode pathname', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com/myyjä');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        myyjä: function() {
+          assert.ok(true);
+        }
+      }
+    });
+    new MyRouter;
+    Backbone.history.start({pushState: true});
+  });
+
+  QUnit.test('unicode pathname with % in a parameter', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com/myyjä/foo%20%25%3F%2f%40%25%20bar');
+    location.pathname = '/myyj%C3%A4/foo%20%25%3F%2f%40%25%20bar';
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        'myyjä/:query': function(query) {
+          assert.strictEqual(query, 'foo %?/@% bar');
+        }
+      }
+    });
+    new MyRouter;
+    Backbone.history.start({pushState: true});
+  });
+
+  QUnit.test('newline in route', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com/stuff%0Anonsense?param=foo%0Abar');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        'stuff\nnonsense': function() {
+          assert.ok(true);
+        }
+      }
+    });
+    new MyRouter;
+    Backbone.history.start({pushState: true});
+  });
+
+  QUnit.test('Router#execute receives callback, args, name.', function(assert) {
+    assert.expect(3);
+    location.replace('http://example.com#foo/123/bar?x=y');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {'foo/:id/bar': 'foo'},
+      foo: function(){},
+      execute: function(callback, args, name) {
+        assert.strictEqual(callback, this.foo);
+        assert.deepEqual(args, ['123', 'x=y']);
+        assert.strictEqual(name, 'foo');
+      }
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.start();
+  });
+
+  QUnit.test('pushState to hashChange with only search params.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com?a=b');
+    location.replace = function(url) {
+      assert.strictEqual(url, '/#?a=b');
+    };
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: null
+    });
+    Backbone.history.start({pushState: true});
+  });
+
+  QUnit.test('#3123 - History#navigate decodes before comparison.', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/shop/search?keyword=short%20dress');
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: function(){ assert.ok(false); },
+        replaceState: function(){ assert.ok(false); }
+      }
+    });
+    Backbone.history.start({pushState: true});
+    Backbone.history.navigate('shop/search?keyword=short%20dress', true);
+    assert.strictEqual(Backbone.history.fragment, 'shop/search?keyword=short dress');
+  });
+
+  QUnit.test('#3175 - Urls in the params', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com#login?a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db');
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var myRouter = new Backbone.Router;
+    myRouter.route('login', function(params) {
+      assert.strictEqual(params, 'a=value&backUrl=https%3A%2F%2Fwww.msn.com%2Fidp%2Fidpdemo%3Fspid%3Dspdemo%26target%3Db');
+    });
+    Backbone.history.start();
+  });
+
+  QUnit.test('#3358 - pushState to hashChange transition with search params', function(assert) {
+    assert.expect(1);
+    Backbone.history.stop();
+    location.replace('http://example.com/root?foo=bar');
+    location.replace = function(url) {
+      assert.strictEqual(url, '/root#?foo=bar');
+    };
+    Backbone.history = _.extend(new Backbone.History, {
+      location: location,
+      history: {
+        pushState: undefined,
+        replaceState: undefined
+      }
+    });
+    Backbone.history.start({root: '/root', pushState: true});
+  });
+
+  QUnit.test("Paths that don't match the root should not match no root", function(assert) {
+    assert.expect(0);
+    location.replace('http://example.com/foo');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        foo: function(){
+          assert.ok(false, 'should not match unless root matches');
+        }
+      }
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.start({root: 'root', pushState: true});
+  });
+
+  QUnit.test("Paths that don't match the root should not match roots of the same length", function(assert) {
+    assert.expect(0);
+    location.replace('http://example.com/xxxx/foo');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {
+        foo: function(){
+          assert.ok(false, 'should not match unless root matches');
+        }
+      }
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.start({root: 'root', pushState: true});
+  });
+
+  QUnit.test('roots with regex characters', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com/x+y.z/foo');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {foo: function(){ assert.ok(true); }}
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.start({root: 'x+y.z', pushState: true});
+  });
+
+  QUnit.test('roots with unicode characters', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com/®ooτ/foo');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {foo: function(){ assert.ok(true); }}
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.start({root: '®ooτ', pushState: true});
+  });
+
+  QUnit.test('roots without slash', function(assert) {
+    assert.expect(1);
+    location.replace('http://example.com/®ooτ');
+    Backbone.history.stop();
+    Backbone.history = _.extend(new Backbone.History, {location: location});
+    var MyRouter = Backbone.Router.extend({
+      routes: {'': function(){ assert.ok(true); }}
+    });
+    var myRouter = new MyRouter;
+    Backbone.history.start({root: '®ooτ', pushState: true});
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/setup/dom-setup.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/setup/dom-setup.js
new file mode 100644
index 0000000..f224228
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/setup/dom-setup.js
@@ -0,0 +1,4 @@
+$('body').append(
+    '<div id="qunit"></div>' +
+    '<div id="qunit-fixture"></div>'
+);
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/setup/environment.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/setup/environment.js
new file mode 100644
index 0000000..c2441ac
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/setup/environment.js
@@ -0,0 +1,45 @@
+(function() {
+
+  var sync = Backbone.sync;
+  var ajax = Backbone.ajax;
+  var emulateHTTP = Backbone.emulateHTTP;
+  var emulateJSON = Backbone.emulateJSON;
+  var history = window.history;
+  var pushState = history.pushState;
+  var replaceState = history.replaceState;
+
+  QUnit.config.noglobals = true;
+
+  QUnit.testStart(function() {
+    var env = QUnit.config.current.testEnvironment;
+
+    // We never want to actually call these during tests.
+    history.pushState = history.replaceState = function(){};
+
+    // Capture ajax settings for comparison.
+    Backbone.ajax = function(settings) {
+      env.ajaxSettings = settings;
+    };
+
+    // Capture the arguments to Backbone.sync for comparison.
+    Backbone.sync = function(method, model, options) {
+      env.syncArgs = {
+        method: method,
+        model: model,
+        options: options
+      };
+      sync.apply(this, arguments);
+    };
+
+  });
+
+  QUnit.testDone(function() {
+    Backbone.sync = sync;
+    Backbone.ajax = ajax;
+    Backbone.emulateHTTP = emulateHTTP;
+    Backbone.emulateJSON = emulateJSON;
+    history.pushState = pushState;
+    history.replaceState = replaceState;
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/sync.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/sync.js
new file mode 100644
index 0000000..8813f15
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/sync.js
@@ -0,0 +1,239 @@
+(function() {
+
+  var Library = Backbone.Collection.extend({
+    url: function() { return '/library'; }
+  });
+  var library;
+
+  var attrs = {
+    title: 'The Tempest',
+    author: 'Bill Shakespeare',
+    length: 123
+  };
+
+  QUnit.module('Backbone.sync', {
+
+    beforeEach: function(assert) {
+      library = new Library;
+      library.create(attrs, {wait: false});
+    },
+
+    afterEach: function(assert) {
+      Backbone.emulateHTTP = false;
+    }
+
+  });
+
+  QUnit.test('read', function(assert) {
+    assert.expect(4);
+    library.fetch();
+    assert.equal(this.ajaxSettings.url, '/library');
+    assert.equal(this.ajaxSettings.type, 'GET');
+    assert.equal(this.ajaxSettings.dataType, 'json');
+    assert.ok(_.isEmpty(this.ajaxSettings.data));
+  });
+
+  QUnit.test('passing data', function(assert) {
+    assert.expect(3);
+    library.fetch({data: {a: 'a', one: 1}});
+    assert.equal(this.ajaxSettings.url, '/library');
+    assert.equal(this.ajaxSettings.data.a, 'a');
+    assert.equal(this.ajaxSettings.data.one, 1);
+  });
+
+  QUnit.test('create', function(assert) {
+    assert.expect(6);
+    assert.equal(this.ajaxSettings.url, '/library');
+    assert.equal(this.ajaxSettings.type, 'POST');
+    assert.equal(this.ajaxSettings.dataType, 'json');
+    var data = JSON.parse(this.ajaxSettings.data);
+    assert.equal(data.title, 'The Tempest');
+    assert.equal(data.author, 'Bill Shakespeare');
+    assert.equal(data.length, 123);
+  });
+
+  QUnit.test('update', function(assert) {
+    assert.expect(7);
+    library.first().save({id: '1-the-tempest', author: 'William Shakespeare'});
+    assert.equal(this.ajaxSettings.url, '/library/1-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'PUT');
+    assert.equal(this.ajaxSettings.dataType, 'json');
+    var data = JSON.parse(this.ajaxSettings.data);
+    assert.equal(data.id, '1-the-tempest');
+    assert.equal(data.title, 'The Tempest');
+    assert.equal(data.author, 'William Shakespeare');
+    assert.equal(data.length, 123);
+  });
+
+  QUnit.test('update with emulateHTTP and emulateJSON', function(assert) {
+    assert.expect(7);
+    library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, {
+      emulateHTTP: true,
+      emulateJSON: true
+    });
+    assert.equal(this.ajaxSettings.url, '/library/2-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'POST');
+    assert.equal(this.ajaxSettings.dataType, 'json');
+    assert.equal(this.ajaxSettings.data._method, 'PUT');
+    var data = JSON.parse(this.ajaxSettings.data.model);
+    assert.equal(data.id, '2-the-tempest');
+    assert.equal(data.author, 'Tim Shakespeare');
+    assert.equal(data.length, 123);
+  });
+
+  QUnit.test('update with just emulateHTTP', function(assert) {
+    assert.expect(6);
+    library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, {
+      emulateHTTP: true
+    });
+    assert.equal(this.ajaxSettings.url, '/library/2-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'POST');
+    assert.equal(this.ajaxSettings.contentType, 'application/json');
+    var data = JSON.parse(this.ajaxSettings.data);
+    assert.equal(data.id, '2-the-tempest');
+    assert.equal(data.author, 'Tim Shakespeare');
+    assert.equal(data.length, 123);
+  });
+
+  QUnit.test('update with just emulateJSON', function(assert) {
+    assert.expect(6);
+    library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'}, {
+      emulateJSON: true
+    });
+    assert.equal(this.ajaxSettings.url, '/library/2-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'PUT');
+    assert.equal(this.ajaxSettings.contentType, 'application/x-www-form-urlencoded');
+    var data = JSON.parse(this.ajaxSettings.data.model);
+    assert.equal(data.id, '2-the-tempest');
+    assert.equal(data.author, 'Tim Shakespeare');
+    assert.equal(data.length, 123);
+  });
+
+  QUnit.test('read model', function(assert) {
+    assert.expect(3);
+    library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+    library.first().fetch();
+    assert.equal(this.ajaxSettings.url, '/library/2-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'GET');
+    assert.ok(_.isEmpty(this.ajaxSettings.data));
+  });
+
+  QUnit.test('destroy', function(assert) {
+    assert.expect(3);
+    library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+    library.first().destroy({wait: true});
+    assert.equal(this.ajaxSettings.url, '/library/2-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'DELETE');
+    assert.equal(this.ajaxSettings.data, null);
+  });
+
+  QUnit.test('destroy with emulateHTTP', function(assert) {
+    assert.expect(3);
+    library.first().save({id: '2-the-tempest', author: 'Tim Shakespeare'});
+    library.first().destroy({
+      emulateHTTP: true,
+      emulateJSON: true
+    });
+    assert.equal(this.ajaxSettings.url, '/library/2-the-tempest');
+    assert.equal(this.ajaxSettings.type, 'POST');
+    assert.equal(JSON.stringify(this.ajaxSettings.data), '{"_method":"DELETE"}');
+  });
+
+  QUnit.test('urlError', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model();
+    assert.raises(function() {
+      model.fetch();
+    });
+    model.fetch({url: '/one/two'});
+    assert.equal(this.ajaxSettings.url, '/one/two');
+  });
+
+  QUnit.test('#1052 - `options` is optional.', function(assert) {
+    assert.expect(0);
+    var model = new Backbone.Model();
+    model.url = '/test';
+    Backbone.sync('create', model);
+  });
+
+  QUnit.test('Backbone.ajax', function(assert) {
+    assert.expect(1);
+    Backbone.ajax = function(settings){
+      assert.strictEqual(settings.url, '/test');
+    };
+    var model = new Backbone.Model();
+    model.url = '/test';
+    Backbone.sync('create', model);
+  });
+
+  QUnit.test('Call provided error callback on error.', function(assert) {
+    assert.expect(1);
+    var model = new Backbone.Model;
+    model.url = '/test';
+    Backbone.sync('read', model, {
+      error: function() { assert.ok(true); }
+    });
+    this.ajaxSettings.error();
+  });
+
+  QUnit.test('Use Backbone.emulateHTTP as default.', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model;
+    model.url = '/test';
+
+    Backbone.emulateHTTP = true;
+    model.sync('create', model);
+    assert.strictEqual(this.ajaxSettings.emulateHTTP, true);
+
+    Backbone.emulateHTTP = false;
+    model.sync('create', model);
+    assert.strictEqual(this.ajaxSettings.emulateHTTP, false);
+  });
+
+  QUnit.test('Use Backbone.emulateJSON as default.', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model;
+    model.url = '/test';
+
+    Backbone.emulateJSON = true;
+    model.sync('create', model);
+    assert.strictEqual(this.ajaxSettings.emulateJSON, true);
+
+    Backbone.emulateJSON = false;
+    model.sync('create', model);
+    assert.strictEqual(this.ajaxSettings.emulateJSON, false);
+  });
+
+  QUnit.test('#1756 - Call user provided beforeSend function.', function(assert) {
+    assert.expect(4);
+    Backbone.emulateHTTP = true;
+    var model = new Backbone.Model;
+    model.url = '/test';
+    var xhr = {
+      setRequestHeader: function(header, value) {
+        assert.strictEqual(header, 'X-HTTP-Method-Override');
+        assert.strictEqual(value, 'DELETE');
+      }
+    };
+    model.sync('delete', model, {
+      beforeSend: function(_xhr) {
+        assert.ok(_xhr === xhr);
+        return false;
+      }
+    });
+    assert.strictEqual(this.ajaxSettings.beforeSend(xhr), false);
+  });
+
+  QUnit.test('#2928 - Pass along `textStatus` and `errorThrown`.', function(assert) {
+    assert.expect(2);
+    var model = new Backbone.Model;
+    model.url = '/test';
+    model.on('error', function(m, xhr, options) {
+      assert.strictEqual(options.textStatus, 'textStatus');
+      assert.strictEqual(options.errorThrown, 'errorThrown');
+    });
+    model.fetch();
+    this.ajaxSettings.error({}, 'textStatus', 'errorThrown');
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/view.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/view.js
new file mode 100644
index 0000000..faf3445
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/backbone/test/view.js
@@ -0,0 +1,495 @@
+(function() {
+
+  var view;
+
+  QUnit.module('Backbone.View', {
+
+    beforeEach: function(assert) {
+      $('#qunit-fixture').append(
+        '<div id="testElement"><h1>Test</h1></div>'
+      );
+
+      view = new Backbone.View({
+        id: 'test-view',
+        className: 'test-view',
+        other: 'non-special-option'
+      });
+    },
+
+    afterEach: function() {
+      $('#testElement').remove();
+      $('#test-view').remove();
+    }
+
+  });
+
+  QUnit.test('constructor', function(assert) {
+    assert.expect(3);
+    assert.equal(view.el.id, 'test-view');
+    assert.equal(view.el.className, 'test-view');
+    assert.equal(view.el.other, void 0);
+  });
+
+  QUnit.test('$', function(assert) {
+    assert.expect(2);
+    var myView = new Backbone.View;
+    myView.setElement('<p><a><b>test</b></a></p>');
+    var result = myView.$('a b');
+
+    assert.strictEqual(result[0].innerHTML, 'test');
+    assert.ok(result.length === +result.length);
+  });
+
+  QUnit.test('$el', function(assert) {
+    assert.expect(3);
+    var myView = new Backbone.View;
+    myView.setElement('<p><a><b>test</b></a></p>');
+    assert.strictEqual(myView.el.nodeType, 1);
+
+    assert.ok(myView.$el instanceof Backbone.$);
+    assert.strictEqual(myView.$el[0], myView.el);
+  });
+
+  QUnit.test('initialize', function(assert) {
+    assert.expect(1);
+    var View = Backbone.View.extend({
+      initialize: function() {
+        this.one = 1;
+      }
+    });
+
+    assert.strictEqual(new View().one, 1);
+  });
+
+  QUnit.test('render', function(assert) {
+    assert.expect(1);
+    var myView = new Backbone.View;
+    assert.equal(myView.render(), myView, '#render returns the view instance');
+  });
+
+  QUnit.test('delegateEvents', function(assert) {
+    assert.expect(6);
+    var counter1 = 0, counter2 = 0;
+
+    var myView = new Backbone.View({el: '#testElement'});
+    myView.increment = function(){ counter1++; };
+    myView.$el.on('click', function(){ counter2++; });
+
+    var events = {'click h1': 'increment'};
+
+    myView.delegateEvents(events);
+    myView.$('h1').trigger('click');
+    assert.equal(counter1, 1);
+    assert.equal(counter2, 1);
+
+    myView.$('h1').trigger('click');
+    assert.equal(counter1, 2);
+    assert.equal(counter2, 2);
+
+    myView.delegateEvents(events);
+    myView.$('h1').trigger('click');
+    assert.equal(counter1, 3);
+    assert.equal(counter2, 3);
+  });
+
+  QUnit.test('delegate', function(assert) {
+    assert.expect(3);
+    var myView = new Backbone.View({el: '#testElement'});
+    myView.delegate('click', 'h1', function() {
+      assert.ok(true);
+    });
+    myView.delegate('click', function() {
+      assert.ok(true);
+    });
+    myView.$('h1').trigger('click');
+
+    assert.equal(myView.delegate(), myView, '#delegate returns the view instance');
+  });
+
+  QUnit.test('delegateEvents allows functions for callbacks', function(assert) {
+    assert.expect(3);
+    var myView = new Backbone.View({el: '<p></p>'});
+    myView.counter = 0;
+
+    var events = {
+      click: function() {
+        this.counter++;
+      }
+    };
+
+    myView.delegateEvents(events);
+    myView.$el.trigger('click');
+    assert.equal(myView.counter, 1);
+
+    myView.$el.trigger('click');
+    assert.equal(myView.counter, 2);
+
+    myView.delegateEvents(events);
+    myView.$el.trigger('click');
+    assert.equal(myView.counter, 3);
+  });
+
+
+  QUnit.test('delegateEvents ignore undefined methods', function(assert) {
+    assert.expect(0);
+    var myView = new Backbone.View({el: '<p></p>'});
+    myView.delegateEvents({'click': 'undefinedMethod'});
+    myView.$el.trigger('click');
+  });
+
+  QUnit.test('undelegateEvents', function(assert) {
+    assert.expect(7);
+    var counter1 = 0, counter2 = 0;
+
+    var myView = new Backbone.View({el: '#testElement'});
+    myView.increment = function(){ counter1++; };
+    myView.$el.on('click', function(){ counter2++; });
+
+    var events = {'click h1': 'increment'};
+
+    myView.delegateEvents(events);
+    myView.$('h1').trigger('click');
+    assert.equal(counter1, 1);
+    assert.equal(counter2, 1);
+
+    myView.undelegateEvents();
+    myView.$('h1').trigger('click');
+    assert.equal(counter1, 1);
+    assert.equal(counter2, 2);
+
+    myView.delegateEvents(events);
+    myView.$('h1').trigger('click');
+    assert.equal(counter1, 2);
+    assert.equal(counter2, 3);
+
+    assert.equal(myView.undelegateEvents(), myView, '#undelegateEvents returns the view instance');
+  });
+
+  QUnit.test('undelegate', function(assert) {
+    assert.expect(1);
+    var myView = new Backbone.View({el: '#testElement'});
+    myView.delegate('click', function() { assert.ok(false); });
+    myView.delegate('click', 'h1', function() { assert.ok(false); });
+
+    myView.undelegate('click');
+
+    myView.$('h1').trigger('click');
+    myView.$el.trigger('click');
+
+    assert.equal(myView.undelegate(), myView, '#undelegate returns the view instance');
+  });
+
+  QUnit.test('undelegate with passed handler', function(assert) {
+    assert.expect(1);
+    var myView = new Backbone.View({el: '#testElement'});
+    var listener = function() { assert.ok(false); };
+    myView.delegate('click', listener);
+    myView.delegate('click', function() { assert.ok(true); });
+    myView.undelegate('click', listener);
+    myView.$el.trigger('click');
+  });
+
+  QUnit.test('undelegate with selector', function(assert) {
+    assert.expect(2);
+    var myView = new Backbone.View({el: '#testElement'});
+    myView.delegate('click', function() { assert.ok(true); });
+    myView.delegate('click', 'h1', function() { assert.ok(false); });
+    myView.undelegate('click', 'h1');
+    myView.$('h1').trigger('click');
+    myView.$el.trigger('click');
+  });
+
+  QUnit.test('undelegate with handler and selector', function(assert) {
+    assert.expect(2);
+    var myView = new Backbone.View({el: '#testElement'});
+    myView.delegate('click', function() { assert.ok(true); });
+    var handler = function(){ assert.ok(false); };
+    myView.delegate('click', 'h1', handler);
+    myView.undelegate('click', 'h1', handler);
+    myView.$('h1').trigger('click');
+    myView.$el.trigger('click');
+  });
+
+  QUnit.test('tagName can be provided as a string', function(assert) {
+    assert.expect(1);
+    var View = Backbone.View.extend({
+      tagName: 'span'
+    });
+
+    assert.equal(new View().el.tagName, 'SPAN');
+  });
+
+  QUnit.test('tagName can be provided as a function', function(assert) {
+    assert.expect(1);
+    var View = Backbone.View.extend({
+      tagName: function() {
+        return 'p';
+      }
+    });
+
+    assert.ok(new View().$el.is('p'));
+  });
+
+  QUnit.test('_ensureElement with DOM node el', function(assert) {
+    assert.expect(1);
+    var View = Backbone.View.extend({
+      el: document.body
+    });
+
+    assert.equal(new View().el, document.body);
+  });
+
+  QUnit.test('_ensureElement with string el', function(assert) {
+    assert.expect(3);
+    var View = Backbone.View.extend({
+      el: 'body'
+    });
+    assert.strictEqual(new View().el, document.body);
+
+    View = Backbone.View.extend({
+      el: '#testElement > h1'
+    });
+    assert.strictEqual(new View().el, $('#testElement > h1').get(0));
+
+    View = Backbone.View.extend({
+      el: '#nonexistent'
+    });
+    assert.ok(!new View().el);
+  });
+
+  QUnit.test('with className and id functions', function(assert) {
+    assert.expect(2);
+    var View = Backbone.View.extend({
+      className: function() {
+        return 'className';
+      },
+      id: function() {
+        return 'id';
+      }
+    });
+
+    assert.strictEqual(new View().el.className, 'className');
+    assert.strictEqual(new View().el.id, 'id');
+  });
+
+  QUnit.test('with attributes', function(assert) {
+    assert.expect(2);
+    var View = Backbone.View.extend({
+      attributes: {
+        'id': 'id',
+        'class': 'class'
+      }
+    });
+
+    assert.strictEqual(new View().el.className, 'class');
+    assert.strictEqual(new View().el.id, 'id');
+  });
+
+  QUnit.test('with attributes as a function', function(assert) {
+    assert.expect(1);
+    var View = Backbone.View.extend({
+      attributes: function() {
+        return {'class': 'dynamic'};
+      }
+    });
+
+    assert.strictEqual(new View().el.className, 'dynamic');
+  });
+
+  QUnit.test('should default to className/id properties', function(assert) {
+    assert.expect(4);
+    var View = Backbone.View.extend({
+      className: 'backboneClass',
+      id: 'backboneId',
+      attributes: {
+        'class': 'attributeClass',
+        'id': 'attributeId'
+      }
+    });
+
+    var myView = new View;
+    assert.strictEqual(myView.el.className, 'backboneClass');
+    assert.strictEqual(myView.el.id, 'backboneId');
+    assert.strictEqual(myView.$el.attr('class'), 'backboneClass');
+    assert.strictEqual(myView.$el.attr('id'), 'backboneId');
+  });
+
+  QUnit.test('multiple views per element', function(assert) {
+    assert.expect(3);
+    var count = 0;
+    var $el = $('<p></p>');
+
+    var View = Backbone.View.extend({
+      el: $el,
+      events: {
+        click: function() {
+          count++;
+        }
+      }
+    });
+
+    var view1 = new View;
+    $el.trigger('click');
+    assert.equal(1, count);
+
+    var view2 = new View;
+    $el.trigger('click');
+    assert.equal(3, count);
+
+    view1.delegateEvents();
+    $el.trigger('click');
+    assert.equal(5, count);
+  });
+
+  QUnit.test('custom events', function(assert) {
+    assert.expect(2);
+    var View = Backbone.View.extend({
+      el: $('body'),
+      events: {
+        fake$event: function() { assert.ok(true); }
+      }
+    });
+
+    var myView = new View;
+    $('body').trigger('fake$event').trigger('fake$event');
+
+    $('body').off('fake$event');
+    $('body').trigger('fake$event');
+  });
+
+  QUnit.test('#1048 - setElement uses provided object.', function(assert) {
+    assert.expect(2);
+    var $el = $('body');
+
+    var myView = new Backbone.View({el: $el});
+    assert.ok(myView.$el === $el);
+
+    myView.setElement($el = $($el));
+    assert.ok(myView.$el === $el);
+  });
+
+  QUnit.test('#986 - Undelegate before changing element.', function(assert) {
+    assert.expect(1);
+    var button1 = $('<button></button>');
+    var button2 = $('<button></button>');
+
+    var View = Backbone.View.extend({
+      events: {
+        click: function(e) {
+          assert.ok(myView.el === e.target);
+        }
+      }
+    });
+
+    var myView = new View({el: button1});
+    myView.setElement(button2);
+
+    button1.trigger('click');
+    button2.trigger('click');
+  });
+
+  QUnit.test('#1172 - Clone attributes object', function(assert) {
+    assert.expect(2);
+    var View = Backbone.View.extend({
+      attributes: {foo: 'bar'}
+    });
+
+    var view1 = new View({id: 'foo'});
+    assert.strictEqual(view1.el.id, 'foo');
+
+    var view2 = new View();
+    assert.ok(!view2.el.id);
+  });
+
+  QUnit.test('views stopListening', function(assert) {
+    assert.expect(0);
+    var View = Backbone.View.extend({
+      initialize: function() {
+        this.listenTo(this.model, 'all x', function(){ assert.ok(false); });
+        this.listenTo(this.collection, 'all x', function(){ assert.ok(false); });
+      }
+    });
+
+    var myView = new View({
+      model: new Backbone.Model,
+      collection: new Backbone.Collection
+    });
+
+    myView.stopListening();
+    myView.model.trigger('x');
+    myView.collection.trigger('x');
+  });
+
+  QUnit.test('Provide function for el.', function(assert) {
+    assert.expect(2);
+    var View = Backbone.View.extend({
+      el: function() {
+        return '<p><a></a></p>';
+      }
+    });
+
+    var myView = new View;
+    assert.ok(myView.$el.is('p'));
+    assert.ok(myView.$el.has('a'));
+  });
+
+  QUnit.test('events passed in options', function(assert) {
+    assert.expect(1);
+    var counter = 0;
+
+    var View = Backbone.View.extend({
+      el: '#testElement',
+      increment: function() {
+        counter++;
+      }
+    });
+
+    var myView = new View({
+      events: {
+        'click h1': 'increment'
+      }
+    });
+
+    myView.$('h1').trigger('click').trigger('click');
+    assert.equal(counter, 2);
+  });
+
+  QUnit.test('remove', function(assert) {
+    assert.expect(2);
+    var myView = new Backbone.View;
+    document.body.appendChild(view.el);
+
+    myView.delegate('click', function() { assert.ok(false); });
+    myView.listenTo(myView, 'all x', function() { assert.ok(false); });
+
+    assert.equal(myView.remove(), myView, '#remove returns the view instance');
+    myView.$el.trigger('click');
+    myView.trigger('x');
+
+    // In IE8 and below, parentNode still exists but is not document.body.
+    assert.notEqual(myView.el.parentNode, document.body);
+  });
+
+  QUnit.test('setElement', function(assert) {
+    assert.expect(3);
+    var myView = new Backbone.View({
+      events: {
+        click: function() { assert.ok(false); }
+      }
+    });
+    myView.events = {
+      click: function() { assert.ok(true); }
+    };
+    var oldEl = myView.el;
+    var $oldEl = myView.$el;
+
+    myView.setElement(document.createElement('div'));
+
+    $oldEl.click();
+    myView.$el.click();
+
+    assert.notEqual(oldEl, myView.el);
+    assert.notEqual($oldEl, myView.$el);
+  });
+
+})();
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/license.txt b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/license.txt
new file mode 100644
index 0000000..ba43b75
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/license.txt
@@ -0,0 +1,30 @@
+Software License Agreement (BSD License)
+
+Copyright (c) 2007, Parakey Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or without modification,
+are permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of Parakey Inc. nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of Parakey Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
+IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
+FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
+IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
+OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/blank.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/blank.gif
new file mode 100644
index 0000000..6865c96
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/blank.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBg.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBg.png
new file mode 100644
index 0000000..f367b42
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBg.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png
new file mode 100644
index 0000000..cd37a0d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/buttonBgHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css
new file mode 100644
index 0000000..ba55c7e
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/debugger.css
@@ -0,0 +1,331 @@
+/* See license.txt for terms of usage */
+
+.panelNode-script {
+    overflow: hidden;
+    font-family: monospace;
+}
+
+/************************************************************************************************/
+
+.scriptTooltip {
+    position: fixed;
+    z-index: 2147483647;
+    padding: 2px 3px;
+    border: 1px solid #CBE087;
+    background: LightYellow;
+    font-family: monospace;
+    color: #000000;
+}
+
+/************************************************************************************************/
+
+.sourceBox {
+    /* TODO: xxxpedro problem with sourceBox and scrolling elements */
+    /*overflow: scroll; /* see issue 1479 */
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 100%;
+}
+
+.sourceRow {
+    white-space: nowrap;
+    -moz-user-select: text;
+}
+
+.sourceRow.hovered {
+    background-color: #EEEEEE;
+}
+
+/************************************************************************************************/
+
+.sourceLine {
+    -moz-user-select: none;
+    margin-right: 10px;
+    border-right: 1px solid #CCCCCC;
+    padding: 0px 4px 0 20px;
+    background: #EEEEEE no-repeat 2px 0px;
+    color: #888888;
+    white-space: pre;
+    font-family: monospace; /* see issue 2953 */
+}
+
+.noteInToolTip { /* below sourceLine, so it overrides it */
+    background-color: #FFD472;
+}
+
+.useA11y .sourceBox .sourceViewport:focus .sourceLine {
+    background-color: #FFFFC0;
+    color: navy;
+    border-right: 1px solid black;
+}
+
+.useA11y .sourceBox .sourceViewport:focus {
+    outline: none;
+}
+
+.a11y1emSize {
+    width: 1em;
+    height: 1em;
+    position: absolute;
+}
+
+.useA11y .panelStatusLabel:focus {
+    outline-offset: -2px !important;
+ }
+
+.sourceBox > .sourceRow > .sourceLine {
+    cursor: pointer;
+}
+
+.sourceLine:hover {
+    text-decoration: none;
+}
+
+.sourceRowText {
+    white-space: pre;
+}
+
+.sourceRow[exe_line="true"] {
+    outline: 1px solid #D9D9B6;
+    margin-right: 1px;
+    background-color: lightgoldenrodyellow;
+}
+
+.sourceRow[executable="true"] > .sourceLine {
+    content: "-";
+    color: #4AA02C;  /* Spring Green */
+    font-weight: bold;
+}
+
+.sourceRow[exe_line="true"] > .sourceLine {
+    background-image: url(chrome://firebug/skin/exe.png);
+    color: #000000;
+}
+
+.sourceRow[breakpoint="true"] > .sourceLine {
+    background-image: url(chrome://firebug/skin/breakpoint.png);
+}
+
+.sourceRow[breakpoint="true"][condition="true"] > .sourceLine {
+    background-image: url(chrome://firebug/skin/breakpointCondition.png);
+}
+
+.sourceRow[breakpoint="true"][disabledBreakpoint="true"] > .sourceLine {
+    background-image: url(chrome://firebug/skin/breakpointDisabled.png);
+}
+
+.sourceRow[breakpoint="true"][exe_line="true"] > .sourceLine {
+    background-image: url(chrome://firebug/skin/breakpointExe.png);
+}
+
+.sourceRow[breakpoint="true"][exe_line="true"][disabledBreakpoint="true"] > .sourceLine {
+    background-image: url(chrome://firebug/skin/breakpointDisabledExe.png);
+}
+
+.sourceLine.editing {
+    background-image: url(chrome://firebug/skin/breakpoint.png);
+}
+
+/************************************************************************************************/
+
+.conditionEditor {
+    z-index: 2147483647;
+    position: absolute;
+    margin-top: 0;
+    left: 2px;
+    width: 90%;
+}
+
+.conditionEditorInner {
+    position: relative;
+    top: -26px;
+    height: 0;
+}
+
+.conditionCaption {
+    margin-bottom: 2px;
+    font-family: Lucida Grande, sans-serif;
+    font-weight: bold;
+    font-size: 11px;
+    color: #226679;
+}
+
+.conditionInput {
+    width: 100%;
+    border: 1px solid #0096C0;
+    font-family: monospace;
+    font-size: inherit;
+}
+
+.conditionEditorInner1 {
+    padding-left: 37px;
+    background: url(condBorders.png) repeat-y;
+}
+
+.conditionEditorInner2 {
+    padding-right: 25px;
+    background: url(condBorders.png) repeat-y 100% 0;
+}
+
+.conditionEditorTop1 {
+    background: url(condCorners.png) no-repeat 100% 0;
+    margin-left: 37px;
+    height: 35px;
+}
+
+.conditionEditorTop2 {
+    position: relative;
+    left: -37px;
+    width: 37px;
+    height: 35px;
+    background: url(condCorners.png) no-repeat;
+}
+
+.conditionEditorBottom1 {
+    background: url(condCorners.png) no-repeat 100% 100%;
+    margin-left: 37px;
+    height: 33px;
+}
+
+.conditionEditorBottom2 {
+    position: relative;    left: -37px;
+    width: 37px;
+    height: 33px;
+    background: url(condCorners.png) no-repeat 0 100%;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.upsideDown {
+    margin-top: 2px;
+}
+
+.upsideDown .conditionEditorInner {
+    top: -8px;
+}
+
+.upsideDown .conditionEditorInner1 {
+    padding-left: 33px;
+    background: url(condBordersUps.png) repeat-y;
+}
+
+.upsideDown .conditionEditorInner2 {
+    padding-right: 25px;
+    background: url(condBordersUps.png) repeat-y 100% 0;
+}
+
+.upsideDown .conditionEditorTop1 {
+    background: url(condCornersUps.png) no-repeat 100% 0;
+    margin-left: 33px;
+    height: 25px;
+}
+
+.upsideDown .conditionEditorTop2 {
+    position: relative;
+    left: -33px;
+    width: 33px;
+    height: 25px;
+    background: url(condCornersUps.png) no-repeat;
+}
+
+.upsideDown .conditionEditorBottom1 {
+    background: url(condCornersUps.png) no-repeat 100% 100%;
+    margin-left: 33px;
+    height: 43px;
+}
+
+.upsideDown .conditionEditorBottom2 {
+    position: relative;
+    left: -33px;
+    width: 33px;
+    height: 43px;
+    background: url(condCornersUps.png) no-repeat 0 100%;
+}
+
+/************************************************************************************************/
+
+.breakpointsGroupListBox {
+  overflow: hidden;
+}
+
+.breakpointBlockHead {
+    position: relative;
+    padding-top: 4px;
+}
+
+.breakpointBlockHead > .checkbox {
+    margin-right: 4px;
+}
+
+.breakpointBlockHead > .objectLink-sourceLink {
+    top: 4px;
+    right: 20px;
+    background-color: #FFFFFF; /* issue 3308 */
+}
+
+.breakpointBlockHead > .closeButton {
+    position: absolute;
+    top: 2px;
+    right: 2px;
+}
+
+.breakpointCheckbox {
+    margin-top: 0;
+    vertical-align: top;
+}
+
+.breakpointName {
+    margin-left: 4px;
+    font-weight: bold;
+}
+
+.breakpointRow[aria-checked="false"] > .breakpointBlockHead > *,
+.breakpointRow[aria-checked="false"] > .breakpointCode {
+    opacity: 0.5;
+}
+
+.breakpointRow[aria-checked="false"] .breakpointCheckbox,
+.breakpointRow[aria-checked="false"] .objectLink-sourceLink,
+.breakpointRow[aria-checked="false"] .closeButton,
+.breakpointRow[aria-checked="false"] .breakpointMutationType {
+    opacity: 1.0 !important;
+}
+
+.breakpointCode {
+    overflow: hidden;
+    white-space: nowrap;
+    padding-left: 24px;
+    padding-bottom: 2px;
+    border-bottom: 1px solid #D7D7D7;
+    font-family: monospace;
+    color: DarkGreen;
+}
+
+.breakpointCondition {
+    white-space: nowrap;
+    padding-left: 24px;
+    padding-bottom: 2px;
+    border-bottom: 1px solid #D7D7D7;
+    font-family: monospace;
+    color: Gray;
+}
+
+.breakpointBlock-breakpoints > .groupHeader {
+    display: none;
+}
+
+.breakpointBlock-monitors > .breakpointCode {
+    padding: 0;
+}
+
+.breakpointBlock-errorBreakpoints .breakpointCheckbox,
+.breakpointBlock-monitors .breakpointCheckbox {
+    display: none;
+}
+
+.breakpointHeader {
+    margin: 0 !important;
+    border-top: none !important;
+}
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/detach.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/detach.png
new file mode 100644
index 0000000..0ddb9a1
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/detach.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/detachHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/detachHover.png
new file mode 100644
index 0000000..e419272
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/detachHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disable.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disable.gif
new file mode 100644
index 0000000..dd9eb0e
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disable.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disable.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disable.png
new file mode 100644
index 0000000..c28bcdf
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disable.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.gif
new file mode 100644
index 0000000..70565a8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.png
new file mode 100644
index 0000000..26fe375
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/disableHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/down.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/down.png
new file mode 100644
index 0000000..acbbd30
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/down.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/downActive.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/downActive.png
new file mode 100644
index 0000000..f4312b2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/downActive.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/downHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/downHover.png
new file mode 100644
index 0000000..8144e63
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/downHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png
new file mode 100644
index 0000000..0c377e3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon-sm.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif
new file mode 100644
index 0000000..8ee8116
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.png
new file mode 100644
index 0000000..2d75261
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/errorIcon.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
new file mode 100644
index 0000000..42f9faf
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug-1.3a2.css
@@ -0,0 +1,817 @@
+.fbBtnPressed {
+    background: #ECEBE3;
+    padding: 3px 6px 2px 7px !important;
+    margin: 1px 0 0 1px;
+    _margin: 1px -1px 0 1px;
+    border: 1px solid #ACA899 !important;
+    border-color: #ACA899 #ECEBE3 #ECEBE3 #ACA899 !important;
+}
+
+.fbToolbarButtons {
+    display: none;
+}
+
+#fbStatusBarBox {
+    display: none;
+}
+
+/************************************************************************************************
+ Error Popup
+*************************************************************************************************/
+#fbErrorPopup {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    height: 19px;
+    width: 75px;
+    background: url(sprite.png) #f1f2ee 0 0;
+    z-index: 999;
+}
+
+#fbErrorPopupContent {
+    position: absolute;
+    right: 0;
+    top: 1px;
+    height: 18px;
+    width: 75px;
+    _width: 74px;
+    border-left: 1px solid #aca899;
+}
+
+#fbErrorIndicator {
+    position: absolute;
+    top: 2px;
+    right: 5px;
+}
+
+
+
+
+
+
+
+
+
+
+.fbBtnInspectActive {
+    background: #aaa;
+    color: #fff !important;
+}
+
+/************************************************************************************************
+ General
+*************************************************************************************************/
+html, body {
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+}
+
+body {
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-size: 11px;
+    background: #fff;    
+}
+
+.clear {
+    clear: both;
+}
+
+/************************************************************************************************
+ Mini Chrome
+*************************************************************************************************/
+#fbMiniChrome {
+    display: none;
+    right: 0;
+    height: 27px;
+    background: url(sprite.png) #f1f2ee 0 0;
+    margin-left: 1px;
+}
+
+#fbMiniContent {
+    display: block;
+    position: relative;
+    left: -1px;
+    right: 0;
+    top: 1px;
+    height: 25px;
+    border-left: 1px solid #aca899;
+}
+
+#fbToolbarSearch {
+    float: right;
+    border: 1px solid #ccc;
+    margin: 0 5px 0 0;
+    background: #fff url(search.png) no-repeat 4px 2px;
+    padding-left: 20px;    
+    font-size: 11px;
+}
+
+#fbToolbarErrors {
+    float: right;
+    margin: 1px 4px 0 0;
+    font-size: 11px;
+}
+
+#fbLeftToolbarErrors {
+    float: left;
+    margin: 7px 0px 0 5px;
+    font-size: 11px;
+}
+
+.fbErrors {
+    padding-left: 20px;
+    height: 14px;
+    background: url(errorIcon.png) no-repeat;
+    color: #f00;
+    font-weight: bold;    
+}
+
+#fbMiniErrors {
+    display: inline;
+    display: none;
+    float: right;
+    margin: 5px 2px 0 5px;
+}
+
+#fbMiniIcon {
+    float: right;
+    margin: 3px 4px 0;
+    height: 20px;
+    width: 20px;
+    float: right;    
+    background: url(sprite.png) 0 -135px;
+    cursor: pointer;
+}
+
+
+/************************************************************************************************
+ Master Layout
+*************************************************************************************************/
+#fbChrome {
+    position: fixed;
+    overflow: hidden;
+    height: 100%;
+    width: 100%;
+    border-collapse: collapse;
+    background: #fff;
+}
+
+#fbTop {
+    height: 49px;
+}
+
+#fbToolbar {
+    position: absolute;
+    z-index: 5;
+    width: 100%;
+    top: 0;
+    background: url(sprite.png) #f1f2ee 0 0;
+    height: 27px;
+    font-size: 11px;
+    overflow: hidden;
+}
+
+#fbPanelBarBox {
+    top: 27px;
+    position: absolute;
+    z-index: 8;
+    width: 100%;
+    background: url(sprite.png) #dbd9c9 0 -27px;
+    height: 22px;
+}
+
+#fbContent {
+    height: 100%;
+    vertical-align: top;
+}
+
+#fbBottom {
+    height: 18px;
+    background: #fff;
+}
+
+/************************************************************************************************
+ Sub-Layout 
+*************************************************************************************************/
+
+/* fbToolbar 
+*************************************************************************************************/
+#fbToolbarIcon {
+    float: left;
+    padding: 4px 5px 0;
+}
+
+#fbToolbarIcon a {
+    display: block;
+    height: 20px;
+    width: 20px;
+    background: url(sprite.png) 0 -135px;
+    text-decoration: none;
+    cursor: default;
+}
+
+#fbToolbarButtons {
+    float: left;
+    padding: 4px 2px 0 5px;
+}
+
+#fbToolbarButtons a {
+    text-decoration: none;
+    display: block;
+    float: left;
+    color: #000;
+    padding: 4px 8px 4px;
+    cursor: default;
+}
+
+#fbToolbarButtons a:hover {
+    color: #333;
+    padding: 3px 7px 3px;
+    border: 1px solid #fff;
+    border-bottom: 1px solid #bbb;
+    border-right: 1px solid #bbb;
+}
+
+#fbStatusBarBox {
+    position: relative;
+    top: 5px;
+    line-height: 19px;
+    cursor: default;    
+}
+
+.fbToolbarSeparator{
+    overflow: hidden;
+    border: 1px solid;
+    border-color: transparent #fff transparent #777;
+    _border-color: #eee #fff #eee #777;
+    height: 7px;
+    margin: 10px 6px 0 0;
+    float: left;
+}
+
+.fbStatusBar span {
+    color: #808080;
+    padding: 0 4px 0 0;
+}
+
+.fbStatusBar span a {
+    text-decoration: none;
+    color: black;
+}
+
+.fbStatusBar span a:hover {
+    color: blue;
+    cursor: pointer;    
+}
+
+
+#fbWindowButtons {
+    position: absolute;
+    white-space: nowrap;
+    right: 0;
+    top: 0;
+    height: 17px;
+    _width: 50px;
+    padding: 5px 0 5px 5px;
+    z-index: 6;
+    background: url(sprite.png) #f1f2ee 0 0;
+}
+
+/* fbPanelBarBox
+*************************************************************************************************/
+
+#fbPanelBar1 {
+    width: 255px; /* fixed width to avoid tabs breaking line */
+    z-index: 8;
+    left: 0;
+    white-space: nowrap;
+    background: url(sprite.png) #dbd9c9 0 -27px;
+    position: absolute;
+    left: 4px;
+}
+
+#fbPanelBar2Box {
+    background: url(sprite.png) #dbd9c9 0 -27px;
+    position: absolute;
+    height: 22px;
+    width: 300px; /* fixed width to avoid tabs breaking line */
+    z-index: 9;
+    right: 0;
+}
+
+#fbPanelBar2 {
+    position: absolute;
+    width: 290px; /* fixed width to avoid tabs breaking line */
+    height: 22px;
+    padding-left: 10px;
+}
+
+/* body 
+*************************************************************************************************/
+.fbPanel {
+    display: none;
+}
+
+#fbPanelBox1, #fbPanelBox2 {
+    max-height: inherit;
+    height: 100%;
+    font-size: 11px;
+}
+
+#fbPanelBox2 {
+    background: #fff;
+}
+
+#fbPanelBox2 {
+    width: 300px;
+    background: #fff;
+}
+
+#fbPanel2 {
+    padding-left: 6px;
+    background: #fff;
+}
+
+.hide {
+    overflow: hidden !important;
+    position: fixed !important;
+    display: none !important;
+    visibility: hidden !important;
+}
+
+/* fbBottom 
+*************************************************************************************************/
+
+#fbCommand {
+    height: 18px;
+}
+
+#fbCommandBox {
+    position: absolute;
+    width: 100%;
+    height: 18px;
+    bottom: 0;
+    overflow: hidden;
+    z-index: 9;
+    background: #fff;
+    border: 0;
+    border-top: 1px solid #ccc;
+}
+
+#fbCommandIcon {
+    position: absolute;
+    color: #00f;
+    top: 2px;
+    left: 7px;
+    display: inline;
+    font: 11px Monaco, monospace;
+    z-index: 10;
+}
+
+#fbCommandLine {
+    position: absolute;
+    width: 100%;
+    top: 0;
+    left: 0;
+    border: 0;
+    margin: 0;
+    padding: 2px 0 2px 32px;
+    font: 11px Monaco, monospace;
+    z-index: 9;
+}
+
+div.fbFitHeight {
+    overflow: auto;
+    _position: absolute;
+}
+
+
+/************************************************************************************************
+ Layout Controls
+*************************************************************************************************/
+
+/* fbToolbar buttons 
+*************************************************************************************************/
+#fbWindowButtons a {
+    font-size: 1px;
+    width: 16px;
+    height: 16px;
+    display: block;
+    float: right;
+    margin-right: 4px;
+    text-decoration: none;
+    cursor: default;
+}
+
+#fbWindow_btClose {
+    background: url(sprite.png) 0 -119px;
+}
+
+#fbWindow_btClose:hover {
+    background: url(sprite.png) -16px -119px;
+}
+
+#fbWindow_btDetach {
+    background: url(sprite.png) -32px -119px;
+}
+
+#fbWindow_btDetach:hover {
+    background: url(sprite.png) -48px -119px;
+}
+
+/* fbPanelBarBox tabs 
+*************************************************************************************************/
+.fbTab {
+    text-decoration: none;
+    display: none;
+    float: left;
+    width: auto;
+    float: left;
+    cursor: default;
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-size: 11px;
+    font-weight: bold;
+    height: 22px;
+    color: #565656;
+}
+
+.fbPanelBar span {
+    display: block;
+    float: left;
+}
+
+.fbPanelBar .fbTabL,.fbPanelBar .fbTabR {
+    height: 22px;
+    width: 8px;
+}
+
+.fbPanelBar .fbTabText {
+    padding: 4px 1px 0;
+}
+
+a.fbTab:hover {
+    background: url(sprite.png) 0 -73px;
+}
+
+a.fbTab:hover .fbTabL {
+    background: url(sprite.png) -16px -96px;
+}
+
+a.fbTab:hover .fbTabR {
+    background: url(sprite.png) -24px -96px;
+}
+
+.fbSelectedTab {
+    background: url(sprite.png) #f1f2ee 0 -50px !important;
+    color: #000;
+}
+
+.fbSelectedTab .fbTabL {
+    background: url(sprite.png) 0 -96px !important;
+}
+
+.fbSelectedTab .fbTabR {
+    background: url(sprite.png) -8px -96px !important;
+}
+
+/* splitters 
+*************************************************************************************************/
+#fbHSplitter {
+    position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 5px;
+    overflow: hidden;
+    cursor: n-resize !important;
+    background: url(pixel_transparent.gif);
+    z-index: 9;
+}
+
+#fbHSplitter.fbOnMovingHSplitter {
+    height: 100%;
+    z-index: 100;
+}
+
+.fbVSplitter {
+    background: #ece9d8;
+    color: #000;
+    border: 1px solid #716f64;
+    border-width: 0 1px;
+    border-left-color: #aca899;
+    width: 4px;
+    cursor: e-resize;
+    overflow: hidden;
+    right: 294px;
+    text-decoration: none;
+    z-index: 9;
+    position: absolute;
+    height: 100%;
+    top: 27px;
+    _width: 6px;
+}
+
+/************************************************************************************************/
+div.lineNo {
+    font: 11px Monaco, monospace;
+    float: left;
+    display: inline;
+    position: relative;
+    margin: 0;
+    padding: 0 5px 0 20px;
+    background: #eee;
+    color: #888;
+    border-right: 1px solid #ccc;
+    text-align: right;
+}
+
+pre.nodeCode {
+    font: 11px Monaco, monospace;
+    margin: 0;
+    padding-left: 10px;
+    overflow: hidden;
+    /*
+    _width: 100%;
+    /**/
+}
+
+/************************************************************************************************/
+.nodeControl {
+    margin-top: 3px;
+    margin-left: -14px;
+    float: left;
+    width: 9px;
+    height: 9px;
+    overflow: hidden;
+    cursor: default;
+    background: url(tree_open.gif);
+    _float: none;
+    _display: inline;
+    _position: absolute;
+}
+
+div.nodeMaximized {
+    background: url(tree_close.gif);
+}
+
+div.objectBox-element {
+    padding: 1px 3px;
+}
+.objectBox-selector{
+    cursor: default;
+}
+
+.selectedElement{
+    background: highlight;
+    /* background: url(roundCorner.svg); Opera */
+    color: #fff !important;
+}
+.selectedElement span{
+    color: #fff !important;
+}
+
+/* Webkit CSS Hack - bug in "highlight" named color */ 
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+    .selectedElement{
+      background: #316AC5;
+      color: #fff !important;
+    }
+}
+
+/************************************************************************************************/
+/************************************************************************************************/
+.logRow * {
+    font-size: 11px;
+}
+
+.logRow {
+    position: relative;
+    border-bottom: 1px solid #D7D7D7;
+    padding: 2px 4px 1px 6px;
+    background-color: #FFFFFF;
+}
+
+.logRow-command {
+    font-family: Monaco, monospace;
+    color: blue;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectBox-number,
+.objectBox-function,
+.objectLink-element,
+.objectLink-textNode,
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+    font-family: Monaco, monospace;
+}
+
+.objectBox-null {
+    padding: 0 2px;
+    border: 1px solid #666666;
+    background-color: #888888;
+    color: #FFFFFF;
+}
+
+.objectBox-string {
+    color: red;
+    white-space: pre;
+}
+
+.objectBox-number {
+    color: #000088;
+}
+
+.objectBox-function {
+    color: DarkGreen;
+}
+
+.objectBox-object {
+    color: DarkGreen;
+    font-weight: bold;
+    font-family: Lucida Grande, sans-serif;
+}
+
+.objectBox-array {
+    color: #000;
+}
+
+/************************************************************************************************/
+.logRow-info,.logRow-error,.logRow-warning {
+    background: #fff no-repeat 2px 2px;
+    padding-left: 20px;
+    padding-bottom: 3px;
+}
+
+.logRow-info {
+    background-image: url(infoIcon.png);
+}
+
+.logRow-warning {
+    background-color: cyan;
+    background-image: url(warningIcon.png);
+}
+
+.logRow-error {
+    background-color: LightYellow;
+    background-image: url(errorIcon.png);
+    color: #f00;
+}
+
+.errorMessage {
+    vertical-align: top;
+    color: #f00;
+}
+
+.objectBox-sourceLink {
+    position: absolute;
+    right: 4px;
+    top: 2px;
+    padding-left: 8px;
+    font-family: Lucida Grande, sans-serif;
+    font-weight: bold;
+    color: #0000FF;
+}
+
+/************************************************************************************************/
+.logRow-group {
+    background: #EEEEEE;
+    border-bottom: none;
+}
+
+.logGroup {
+    background: #EEEEEE;
+}
+
+.logGroupBox {
+    margin-left: 24px;
+    border-top: 1px solid #D7D7D7;
+    border-left: 1px solid #D7D7D7;
+}
+
+/************************************************************************************************/
+.selectorTag,.selectorId,.selectorClass {
+    font-family: Monaco, monospace;
+    font-weight: normal;
+}
+
+.selectorTag {
+    color: #0000FF;
+}
+
+.selectorId {
+    color: DarkBlue;
+}
+
+.selectorClass {
+    color: red;
+}
+
+/************************************************************************************************/
+.objectBox-element {
+    font-family: Monaco, monospace;
+    color: #000088;
+}
+
+.nodeChildren {
+    padding-left: 26px;
+}
+
+.nodeTag {
+    color: blue;
+    cursor: pointer;
+}
+
+.nodeValue {
+    color: #FF0000;
+    font-weight: normal;
+}
+
+.nodeText,.nodeComment {
+    margin: 0 2px;
+    vertical-align: top;
+}
+
+.nodeText {
+    color: #333333;
+    font-family: Monaco, monospace;
+}
+
+.nodeComment {
+    color: DarkGreen;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeHidden, .nodeHidden * {
+    color: #888888;
+}
+
+.nodeHidden .nodeTag {
+    color: #5F82D9;
+}
+
+.nodeHidden .nodeValue {
+    color: #D86060;
+}
+
+.selectedElement .nodeHidden, .selectedElement .nodeHidden * {
+    color: SkyBlue !important;
+}
+
+
+/************************************************************************************************/
+.log-object {
+    /*
+    _position: relative;
+    _height: 100%;
+    /**/
+}
+
+.property {
+    position: relative;
+    clear: both;
+    height: 15px;
+}
+
+.propertyNameCell {
+    vertical-align: top;
+    float: left;
+    width: 28%;
+    position: absolute;
+    left: 0;
+    z-index: 0;
+}
+
+.propertyValueCell {
+    float: right;
+    width: 68%;
+    background: #fff;
+    position: absolute;
+    padding-left: 5px;
+    display: table-cell;
+    right: 0;
+    z-index: 1;
+    /*
+    _position: relative;
+    /**/
+}
+
+.propertyName {
+    font-weight: bold;
+}
+
+.FirebugPopup {
+    height: 100% !important;
+}
+
+.FirebugPopup #fbWindowButtons {
+    display: none !important;
+}
+
+.FirebugPopup #fbHSplitter {
+    display: none !important;
+}
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css
new file mode 100644
index 0000000..13a41d2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.IE6.css
@@ -0,0 +1,20 @@
+/************************************************************************************************/
+#fbToolbarSearch {
+    background-image: url(search.gif) !important;
+}
+/************************************************************************************************/
+.fbErrors {
+    background-image: url(errorIcon.gif) !important;
+}
+/************************************************************************************************/
+.logRow-info {
+    background-image: url(infoIcon.gif) !important;
+}
+
+.logRow-warning {
+    background-image: url(warningIcon.gif) !important;
+}
+
+.logRow-error {
+    background-image: url(errorIcon.gif) !important;
+}
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css
new file mode 100644
index 0000000..a1465ec
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.css
@@ -0,0 +1,3147 @@
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Loose */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*
+.netInfoResponseHeadersTitle, netInfoResponseHeadersBody {
+    display: none;
+}
+/**/
+
+.obscured {
+    left: -999999px !important;
+}
+
+/* IE6 need a separated rule, otherwise it will not recognize it */
+.collapsed {
+    display: none;
+}
+
+[collapsed="true"] {
+    display: none;
+}
+
+#fbCSS {
+    padding: 0 !important;
+}
+
+.cssPropDisable {
+    float: left;
+    display: block;
+    width: 2em;
+    cursor: default;
+}
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* panelBase */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+/************************************************************************************************/
+
+.infoTip {
+    z-index: 2147483647;
+    position: fixed;
+    padding: 2px 3px;
+    border: 1px solid #CBE087;
+    background: LightYellow;
+    font-family: Monaco, monospace;
+    color: #000000;
+    display: none;
+    white-space: nowrap;
+    pointer-events: none;
+}
+
+.infoTip[active="true"] {
+    display: block;
+}
+
+.infoTipLoading {
+    width: 16px;
+    height: 16px;
+    background: url(chrome://firebug/skin/loading_16.gif) no-repeat;
+}
+
+.infoTipImageBox {
+	font-size: 11px;
+    min-width: 100px;
+    text-align: center;
+}
+
+.infoTipCaption {
+	font-size: 11px;
+    font: Monaco, monospace;
+}
+
+.infoTipLoading > .infoTipImage,
+.infoTipLoading > .infoTipCaption {
+    display: none;
+}
+
+/************************************************************************************************/
+
+h1.groupHeader {
+    padding: 2px 4px;
+    margin: 0 0 4px 0;
+    border-top: 1px solid #CCCCCC;
+    border-bottom: 1px solid #CCCCCC;
+    background: #eee url(group.gif) repeat-x;
+    font-size: 11px;
+    font-weight: bold;
+    _position: relative;
+}
+
+/************************************************************************************************/
+
+.inlineEditor,
+.fixedWidthEditor {
+    z-index: 2147483647;
+    position: absolute;
+    display: none;
+}
+
+.inlineEditor {
+    margin-left: -6px;
+    margin-top: -3px;
+    /*
+    _margin-left: -7px;
+    _margin-top: -5px;
+    /**/
+}
+
+.textEditorInner,
+.fixedWidthEditor {
+    margin: 0 0 0 0 !important;
+    padding: 0;
+    border: none !important;
+    font: inherit;
+    text-decoration: inherit;
+    background-color: #FFFFFF;
+}
+
+.fixedWidthEditor {
+    border-top: 1px solid #888888 !important;
+    border-bottom: 1px solid #888888 !important;
+}
+
+.textEditorInner {
+    position: relative;
+    top: -7px;
+    left: -5px;
+    
+    outline: none;
+    resize: none;
+    
+    /*
+    _border: 1px solid #999 !important;
+    _padding: 1px !important;
+    _filter:progid:DXImageTransform.Microsoft.dropshadow(OffX=2, OffY=2, Color="#55404040");
+    /**/
+}
+
+.textEditorInner1 {
+    padding-left: 11px;
+    background: url(textEditorBorders.png) repeat-y;
+    _background: url(textEditorBorders.gif) repeat-y;
+    _overflow: hidden;
+}
+
+.textEditorInner2 {
+    position: relative;
+    padding-right: 2px;
+    background: url(textEditorBorders.png) repeat-y 100% 0;
+    _background: url(textEditorBorders.gif) repeat-y 100% 0;
+    _position: fixed;
+}
+
+.textEditorTop1 {
+    background: url(textEditorCorners.png) no-repeat 100% 0;
+    margin-left: 11px;
+    height: 10px;
+    _background: url(textEditorCorners.gif) no-repeat 100% 0;
+    _overflow: hidden;
+}
+
+.textEditorTop2 {
+    position: relative;
+    left: -11px;
+    width: 11px;
+    height: 10px;
+    background: url(textEditorCorners.png) no-repeat;
+    _background: url(textEditorCorners.gif) no-repeat;
+}
+
+.textEditorBottom1 {
+    position: relative;
+    background: url(textEditorCorners.png) no-repeat 100% 100%;
+    margin-left: 11px;
+    height: 12px;
+    _background: url(textEditorCorners.gif) no-repeat 100% 100%;
+}
+
+.textEditorBottom2 {
+    position: relative;
+    left: -11px;
+    width: 11px;
+    height: 12px;
+    background: url(textEditorCorners.png) no-repeat 0 100%;
+    _background: url(textEditorCorners.gif) no-repeat 0 100%;
+}
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* CSS */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+/* See license.txt for terms of usage */
+
+.panelNode-css {
+    overflow-x: hidden;
+}
+
+.cssSheet > .insertBefore {
+    height: 1.5em;
+}
+
+.cssRule {
+    position: relative;
+    margin: 0;
+    padding: 1em 0 0 6px;
+    font-family: Monaco, monospace;
+    color: #000000;
+}
+
+.cssRule:first-child {
+    padding-top: 6px;
+}
+
+.cssElementRuleContainer {
+    position: relative;
+}
+
+.cssHead {
+    padding-right: 150px;
+}
+
+.cssProp {
+    /*padding-left: 2em;*/
+}
+
+.cssPropName {
+    color: DarkGreen;
+}
+
+.cssPropValue {
+    margin-left: 8px;
+    color: DarkBlue;
+}
+
+.cssOverridden span {
+    text-decoration: line-through;
+}
+
+.cssInheritedRule {
+}
+
+.cssInheritLabel {
+    margin-right: 0.5em;
+    font-weight: bold;
+}
+
+.cssRule .objectLink-sourceLink {
+    top: 0;
+}
+
+.cssProp.editGroup:hover {
+    background: url(disable.png) no-repeat 2px 1px;
+    _background: url(disable.gif) no-repeat 2px 1px;
+}
+
+.cssProp.editGroup.editing {
+    background: none;
+}
+
+.cssProp.disabledStyle {
+    background: url(disableHover.png) no-repeat 2px 1px;
+    _background: url(disableHover.gif) no-repeat 2px 1px;
+    opacity: 1;
+    color: #CCCCCC;
+}
+
+.disabledStyle .cssPropName,
+.disabledStyle .cssPropValue {
+    color: #CCCCCC;
+}
+
+.cssPropValue.editing + .cssSemi,
+.inlineExpander + .cssSemi {
+    display: none;
+}
+
+.cssPropValue.editing {
+    white-space: nowrap;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.stylePropName {
+    font-weight: bold;
+    padding: 0 4px 4px 4px;
+    width: 50%;
+}
+
+.stylePropValue {
+    width: 50%;
+}
+/*
+.useA11y .a11yCSSView .focusRow:focus {
+    outline: none;
+    background-color: transparent
+ }
+ 
+ .useA11y .a11yCSSView .focusRow:focus .cssSelector, 
+ .useA11y .a11yCSSView .focusRow:focus .cssPropName, 
+ .useA11y .a11yCSSView .focusRow:focus .cssPropValue,
+ .useA11y .a11yCSSView .computedStyleRow:focus, 
+ .useA11y .a11yCSSView .groupHeader:focus {
+    outline: 2px solid #FF9933;
+    outline-offset: -2px;
+    background-color: #FFFFD6;
+ }
+ 
+ .useA11y .a11yCSSView .groupHeader:focus {
+    outline-offset: -2px;
+ }
+/**/
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Net */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+/* See license.txt for terms of usage */
+
+.panelNode-net {
+    overflow-x: hidden;
+}
+
+.netTable {
+    width: 100%;
+}
+
+/************************************************************************************************/
+
+.hideCategory-undefined .category-undefined,
+.hideCategory-html .category-html,
+.hideCategory-css .category-css,
+.hideCategory-js .category-js,
+.hideCategory-image .category-image,
+.hideCategory-xhr .category-xhr,
+.hideCategory-flash .category-flash,
+.hideCategory-txt .category-txt,
+.hideCategory-bin .category-bin {
+    display: none;
+}
+
+/************************************************************************************************/
+
+.netHeadRow {
+    background: url(chrome://firebug/skin/group.gif) repeat-x #FFFFFF;
+}
+
+.netHeadCol {
+    border-bottom: 1px solid #CCCCCC;
+    padding: 2px 4px 2px 18px;
+    font-weight: bold;
+}
+
+.netHeadLabel {
+    white-space: nowrap;
+    overflow: hidden;
+}
+
+/************************************************************************************************/
+/* Header for Net panel table */
+
+.netHeaderRow {
+    height: 16px;
+}
+
+.netHeaderCell {
+    cursor: pointer;
+    -moz-user-select: none;
+    border-bottom: 1px solid #9C9C9C;
+    padding: 0 !important;
+    font-weight: bold;
+    background: #BBBBBB url(chrome://firebug/skin/tableHeader.gif) repeat-x;
+    white-space: nowrap;
+}
+
+.netHeaderRow > .netHeaderCell:first-child > .netHeaderCellBox {
+    padding: 2px 14px 2px 18px;
+}
+
+.netHeaderCellBox {
+    padding: 2px 14px 2px 10px;
+    border-left: 1px solid #D9D9D9;
+    border-right: 1px solid #9C9C9C;
+}
+
+.netHeaderCell:hover:active {
+    background: #959595 url(chrome://firebug/skin/tableHeaderActive.gif) repeat-x;
+}
+
+.netHeaderSorted {
+    background: #7D93B2 url(chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;
+}
+
+.netHeaderSorted > .netHeaderCellBox {
+    border-right-color: #6B7C93;
+    background: url(chrome://firebug/skin/arrowDown.png) no-repeat right;
+}
+
+.netHeaderSorted.sortedAscending > .netHeaderCellBox {
+    background-image: url(chrome://firebug/skin/arrowUp.png);
+}
+
+.netHeaderSorted:hover:active {
+    background: #536B90 url(chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;
+}
+
+/************************************************************************************************/
+/* Breakpoints */
+
+.panelNode-net .netRowHeader {
+    display: block;
+}
+
+.netRowHeader {
+    cursor: pointer;
+    display: none;
+    height: 15px;
+    margin-right: 0 !important;
+}
+
+/* Display brekpoint disc */
+.netRow .netRowHeader {
+    background-position: 5px 1px;
+}
+
+.netRow[breakpoint="true"] .netRowHeader {
+    background-image: url(chrome://firebug/skin/breakpoint.png);
+}
+
+.netRow[breakpoint="true"][disabledBreakpoint="true"] .netRowHeader {
+    background-image: url(chrome://firebug/skin/breakpointDisabled.png);
+}
+
+.netRow.category-xhr:hover .netRowHeader {
+    background-color: #F6F6F6;
+}
+
+#netBreakpointBar {
+    max-width: 38px;
+}
+
+#netHrefCol > .netHeaderCellBox {
+    border-left: 0px;
+}
+
+.netRow .netRowHeader {
+    width: 3px;
+}
+
+.netInfoRow .netRowHeader {
+    display: table-cell;
+}
+
+/************************************************************************************************/
+/* Column visibility */
+
+.netTable[hiddenCols~=netHrefCol] TD[id="netHrefCol"],
+.netTable[hiddenCols~=netHrefCol] TD.netHrefCol,
+.netTable[hiddenCols~=netStatusCol] TD[id="netStatusCol"],
+.netTable[hiddenCols~=netStatusCol] TD.netStatusCol,
+.netTable[hiddenCols~=netDomainCol] TD[id="netDomainCol"],
+.netTable[hiddenCols~=netDomainCol] TD.netDomainCol,
+.netTable[hiddenCols~=netSizeCol] TD[id="netSizeCol"],
+.netTable[hiddenCols~=netSizeCol] TD.netSizeCol,
+.netTable[hiddenCols~=netTimeCol] TD[id="netTimeCol"],
+.netTable[hiddenCols~=netTimeCol] TD.netTimeCol {
+    display: none;
+}
+
+/************************************************************************************************/
+
+.netRow {
+    background: LightYellow;
+}
+
+.netRow.loaded {
+    background: #FFFFFF;
+}
+
+.netRow.loaded:hover {
+    background: #EFEFEF;
+}
+
+.netCol {
+    padding: 0;
+    vertical-align: top;
+    border-bottom: 1px solid #EFEFEF;
+    white-space: nowrap;
+    height: 17px;
+}
+
+.netLabel {
+    width: 100%;
+}
+
+.netStatusCol {
+    padding-left: 10px;
+    color: rgb(128, 128, 128);
+}
+
+.responseError > .netStatusCol {
+    color: red;
+}
+
+.netDomainCol {
+    padding-left: 5px;
+}
+
+.netSizeCol {
+    text-align: right;
+    padding-right: 10px;
+}
+
+.netHrefLabel {
+    -moz-box-sizing: padding-box;
+    overflow: hidden;
+    z-index: 10;
+    position: absolute;
+    padding-left: 18px;
+    padding-top: 1px;
+    max-width: 15%;
+    font-weight: bold;
+}
+
+.netFullHrefLabel {
+    display: none;
+    -moz-user-select: none;
+    padding-right: 10px;
+    padding-bottom: 3px;
+    max-width: 100%;
+    background: #FFFFFF;
+    z-index: 200;
+}
+
+.netHrefCol:hover > .netFullHrefLabel {
+    display: block;
+}
+
+.netRow.loaded:hover .netCol > .netFullHrefLabel {
+    background-color: #EFEFEF;
+}
+
+.useA11y .a11yShowFullLabel {
+    display: block;
+    background-image: none !important;
+    border: 1px solid #CBE087;
+    background-color: LightYellow;
+    font-family: Monaco, monospace;
+    color: #000000;
+    font-size: 10px;
+    z-index: 2147483647;
+}
+
+.netSizeLabel {
+    padding-left: 6px;
+}
+
+.netStatusLabel,
+.netDomainLabel,
+.netSizeLabel,
+.netBar {
+    padding: 1px 0 2px 0 !important;
+}
+
+.responseError {
+    color: red;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.hasHeaders .netHrefLabel:hover {
+    cursor: pointer;
+    color: blue;
+    text-decoration: underline;
+}
+
+/************************************************************************************************/
+
+.netLoadingIcon {
+    position: absolute;
+    border: 0;
+    margin-left: 14px;
+    width: 16px;
+    height: 16px;
+    background: transparent no-repeat 0 0;
+    background-image: url(chrome://firebug/skin/loading_16.gif);
+    display:inline-block;
+}
+
+.loaded .netLoadingIcon {
+    display: none;
+}
+
+/************************************************************************************************/
+
+.netBar, .netSummaryBar {
+    position: relative;
+    border-right: 50px solid transparent;
+}
+
+.netResolvingBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: #FFFFFF url(chrome://firebug/skin/netBarResolving.gif) repeat-x;
+    z-index:60;
+}
+
+.netConnectingBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: #FFFFFF url(chrome://firebug/skin/netBarConnecting.gif) repeat-x;
+    z-index:50;
+}
+
+.netBlockingBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: #FFFFFF url(chrome://firebug/skin/netBarWaiting.gif) repeat-x;
+    z-index:40;
+}
+
+.netSendingBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: #FFFFFF url(chrome://firebug/skin/netBarSending.gif) repeat-x;
+    z-index:30;
+}
+
+.netWaitingBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: #FFFFFF url(chrome://firebug/skin/netBarResponded.gif) repeat-x;
+    z-index:20;
+    min-width: 1px;
+}
+
+.netReceivingBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    background: #38D63B url(chrome://firebug/skin/netBarLoading.gif) repeat-x;
+    z-index:10;
+}
+
+.netWindowLoadBar,
+.netContentLoadBar {
+    position: absolute;
+    left: 0;
+    top: 0;
+    bottom: 0;
+    width: 1px;
+    background-color: red;
+    z-index: 70;
+    opacity: 0.5;
+    display: none;
+    margin-bottom:-1px;
+}
+
+.netContentLoadBar {
+    background-color: Blue;
+}
+
+.netTimeLabel {
+    -moz-box-sizing: padding-box;
+    position: absolute;
+    top: 1px;
+    left: 100%;
+    padding-left: 6px;
+    color: #444444;
+    min-width: 16px;
+}
+
+/*
+ * Timing info tip is reusing net timeline styles to display the same
+ * colors for individual request phases. Notice that the info tip must
+ * respect also loaded and fromCache styles that also modify the
+ * actual color. These are used both on the same element in case
+ * of the tooltip.
+ */
+.loaded .netReceivingBar,
+.loaded.netReceivingBar {
+    background: #B6B6B6 url(chrome://firebug/skin/netBarLoaded.gif) repeat-x;
+    border-color: #B6B6B6;
+}
+
+.fromCache .netReceivingBar,
+.fromCache.netReceivingBar {
+    background: #D6D6D6 url(chrome://firebug/skin/netBarCached.gif) repeat-x;
+    border-color: #D6D6D6;
+}
+
+.netSummaryRow .netTimeLabel,
+.loaded .netTimeLabel {
+    background: transparent;
+}
+
+/************************************************************************************************/
+/* Time Info tip */
+
+.timeInfoTip {
+    width: 150px; 
+    height: 40px
+}
+
+.timeInfoTipBar,
+.timeInfoTipEventBar {
+    position: relative;
+    display: block;
+    margin: 0;
+    opacity: 1;
+    height: 15px;
+    width: 4px;
+}
+
+.timeInfoTipEventBar {
+    width: 1px !important;
+}
+
+.timeInfoTipCell.startTime {
+    padding-right: 8px;
+}
+
+.timeInfoTipCell.elapsedTime {
+    text-align: right;
+    padding-right: 8px;
+}
+
+/************************************************************************************************/
+/* Size Info tip */
+
+.sizeInfoLabelCol {
+    font-weight: bold;
+    padding-right: 10px;
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-size: 11px;
+}
+
+.sizeInfoSizeCol {
+    font-weight: bold;
+}
+
+.sizeInfoDetailCol {
+    color: gray;
+    text-align: right;
+}
+
+.sizeInfoDescCol {
+    font-style: italic;
+}
+
+/************************************************************************************************/
+/* Summary */
+
+.netSummaryRow .netReceivingBar {
+    background: #BBBBBB;
+    border: none;
+}
+
+.netSummaryLabel {
+    color: #222222;
+}
+
+.netSummaryRow {
+    background: #BBBBBB !important;
+    font-weight: bold;
+}
+
+.netSummaryRow .netBar {
+    border-right-color: #BBBBBB;
+}
+
+.netSummaryRow > .netCol {
+    border-top: 1px solid #999999;
+    border-bottom: 2px solid;
+    -moz-border-bottom-colors: #EFEFEF #999999;
+    padding-top: 1px;
+    padding-bottom: 2px;
+}
+
+.netSummaryRow > .netHrefCol:hover {
+    background: transparent !important;
+}
+
+.netCountLabel {
+    padding-left: 18px;
+}
+
+.netTotalSizeCol {
+    text-align: right;
+    padding-right: 10px;
+}
+
+.netTotalTimeCol {
+    text-align: right;
+}
+
+.netCacheSizeLabel {
+    position: absolute;
+    z-index: 1000;
+    left: 0;
+    top: 0;
+}
+
+/************************************************************************************************/
+
+.netLimitRow {
+    background: rgb(255, 255, 225) !important;
+    font-weight:normal;
+    color: black;
+    font-weight:normal;
+}
+
+.netLimitLabel {
+    padding-left: 18px;
+}
+
+.netLimitRow > .netCol {
+    border-bottom: 2px solid;
+    -moz-border-bottom-colors: #EFEFEF #999999;
+    vertical-align: middle !important;
+    padding-top: 2px;
+    padding-bottom: 2px;
+}
+
+.netLimitButton {
+    font-size: 11px;
+    padding-top: 1px;
+    padding-bottom: 1px;
+}
+
+/************************************************************************************************/
+
+.netInfoCol {
+    border-top: 1px solid #EEEEEE;
+    background: url(chrome://firebug/skin/group.gif) repeat-x #FFFFFF;
+}
+
+.netInfoBody {
+    margin: 10px 0 4px 10px;
+}
+
+.netInfoTabs {
+    position: relative;
+    padding-left: 17px;
+}
+
+.netInfoTab {
+    position: relative;
+    top: -3px;
+    margin-top: 10px;
+    padding: 4px 6px;
+    border: 1px solid transparent;
+    border-bottom: none;
+    _border: none;
+    font-weight: bold;
+    color: #565656;
+    cursor: pointer;
+}
+
+/*.netInfoTab:hover {
+    cursor: pointer;
+}*/
+
+/* replaced by .netInfoTabSelected for IE6 support
+.netInfoTab[selected="true"] {
+    cursor: default !important;
+    border: 1px solid #D7D7D7 !important;
+    border-bottom: none !important;
+    -moz-border-radius: 4px 4px 0 0;
+    background-color: #FFFFFF;
+}
+/**/
+.netInfoTabSelected {
+    cursor: default !important;
+    border: 1px solid #D7D7D7 !important;
+    border-bottom: none !important;
+    -moz-border-radius: 4px 4px 0 0;
+    -webkit-border-radius: 4px 4px 0 0;
+    border-radius: 4px 4px 0 0;
+    background-color: #FFFFFF;
+}
+
+.logRow-netInfo.error .netInfoTitle {
+    color: red;
+}
+
+.logRow-netInfo.loading .netInfoResponseText {
+    font-style: italic;
+    color: #888888;
+}
+
+.loading .netInfoResponseHeadersTitle {
+    display: none;
+}
+
+.netInfoResponseSizeLimit {
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    padding-top: 10px;
+    font-size: 11px;
+}
+
+.netInfoText {
+    display: none;
+    margin: 0;
+    border: 1px solid #D7D7D7;
+    border-right: none;
+    padding: 8px;
+    background-color: #FFFFFF;
+    font-family: Monaco, monospace;
+    white-space: pre-wrap;
+    /*overflow-x: auto; HTML is damaged in case of big (2-3MB) responses */
+}
+
+/* replaced by .netInfoTextSelected for IE6 support 
+.netInfoText[selected="true"] {
+    display: block;
+}
+/**/
+.netInfoTextSelected {
+    display: block;
+}
+
+.netInfoParamName {
+    padding-right: 10px;
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-weight: bold;
+    vertical-align: top;
+    text-align: right;
+    white-space: nowrap;
+}
+
+.netInfoPostText .netInfoParamName {
+    width: 1px; /* Google Chrome need this otherwise the first column of 
+                   the post variables table will be larger than expected */
+}
+
+.netInfoParamValue {
+    width: 100%;
+}
+
+.netInfoHeadersText,
+.netInfoPostText,
+.netInfoPutText {
+    padding-top: 0;
+}
+
+.netInfoHeadersGroup,
+.netInfoPostParams,
+.netInfoPostSource {
+    margin-bottom: 4px;
+    border-bottom: 1px solid #D7D7D7;
+    padding-top: 8px;
+    padding-bottom: 2px;
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-weight: bold;
+    color: #565656;
+}
+
+.netInfoPostParamsTable,
+.netInfoPostPartsTable,
+.netInfoPostJSONTable,
+.netInfoPostXMLTable,
+.netInfoPostSourceTable {
+    margin-bottom: 10px;
+    width: 100%;
+}
+
+.netInfoPostContentType {
+    color: #bdbdbd;
+    padding-left: 50px;
+    font-weight: normal;
+}
+
+.netInfoHtmlPreview {
+    border: 0;
+    width: 100%;
+    height:100%;
+}
+
+/************************************************************************************************/
+/* Request & Response Headers */
+
+.netHeadersViewSource {
+    color: #bdbdbd;
+    margin-left: 200px;
+    font-weight: normal;
+}
+
+.netHeadersViewSource:hover {
+    color: blue;
+    cursor: pointer;
+}
+
+/************************************************************************************************/
+
+.netActivationRow,
+.netPageSeparatorRow {
+    background: rgb(229, 229, 229) !important;
+    font-weight: normal;
+    color: black;
+}
+
+.netActivationLabel {
+    background: url(chrome://firebug/skin/infoIcon.png) no-repeat 3px 2px;
+    padding-left: 22px;
+}
+
+/************************************************************************************************/
+
+.netPageSeparatorRow {
+    height: 5px !important;
+}
+
+.netPageSeparatorLabel {
+    padding-left: 22px;
+    height: 5px !important;
+}
+
+.netPageRow {
+    background-color: rgb(255, 255, 255);
+}
+
+.netPageRow:hover {
+    background: #EFEFEF;
+}
+
+.netPageLabel {
+    padding: 1px 0 2px 18px !important;
+    font-weight: bold;
+}
+
+/************************************************************************************************/
+
+.netActivationRow > .netCol {
+    border-bottom: 2px solid;
+    -moz-border-bottom-colors: #EFEFEF #999999;
+    padding-top: 2px;
+    padding-bottom: 3px;
+}
+/*
+.useA11y .panelNode-net .a11yFocus:focus,
+.useA11y .panelNode-net .focusRow:focus {
+    outline-offset: -2px;
+    background-color: #FFFFD6 !important;
+}
+
+.useA11y .panelNode-net .netHeaderCell:focus,
+.useA11y .panelNode-net :focus .netHeaderCell,
+.useA11y .panelNode-net :focus .netReceivingBar,
+.useA11y .netSummaryRow :focus .netBar,
+.useA11y .netSummaryRow:focus .netBar {
+    background-color: #FFFFD6;
+    background-image: none;
+    border-color: #FFFFD6;
+}
+/**/
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Windows */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+
+/************************************************************************************************/
+/* Twisties */
+
+.twisty,
+.logRow-errorMessage > .hasTwisty > .errorTitle,
+.logRow-log > .objectBox-array.hasTwisty,
+.logRow-spy .spyHead .spyTitle,
+.logGroup > .logRow,
+.memberRow.hasChildren > .memberLabelCell > .memberLabel,
+.hasHeaders .netHrefLabel,
+.netPageRow > .netCol > .netPageTitle {
+    background-image: url(tree_open.gif);
+    background-repeat: no-repeat;
+    background-position: 2px 2px;
+    min-height: 12px;
+}
+
+.logRow-errorMessage > .hasTwisty.opened > .errorTitle,
+.logRow-log > .objectBox-array.hasTwisty.opened,
+.logRow-spy.opened .spyHead .spyTitle,
+.logGroup.opened > .logRow,
+.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel,
+.nodeBox.highlightOpen > .nodeLabel > .twisty,
+.nodeBox.open > .nodeLabel > .twisty,
+.netRow.opened > .netCol > .netHrefLabel,
+.netPageRow.opened > .netCol > .netPageTitle {
+    background-image: url(tree_close.gif);
+}
+
+.twisty {
+    background-position: 4px 4px;
+}
+
+
+
+/************************************************************************************************/
+/* Twisties IE6 */
+
+/* IE6 has problems with > operator, and multiple classes */
+
+* html .logRow-spy .spyHead .spyTitle,
+* html .logGroup .logGroupLabel,
+* html .hasChildren .memberLabelCell .memberLabel,
+* html .hasHeaders .netHrefLabel {
+    background-image: url(tree_open.gif);
+    background-repeat: no-repeat;
+    background-position: 2px 2px;
+}
+
+* html .opened .spyHead .spyTitle,
+* html .opened .logGroupLabel, 
+* html .opened .memberLabelCell .memberLabel {
+    background-image: url(tree_close.gif);
+    background-repeat: no-repeat;
+    background-position: 2px 2px;
+}
+
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* Console */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+
+/* See license.txt for terms of usage */
+
+.panelNode-console {
+    overflow-x: hidden;
+}
+
+.objectLink {
+    text-decoration: none;
+}
+
+.objectLink:hover {
+    cursor: pointer;
+    text-decoration: underline;
+}
+
+.logRow {
+    position: relative;
+    margin: 0;
+    border-bottom: 1px solid #D7D7D7;
+    padding: 2px 4px 1px 6px;
+    background-color: #FFFFFF;
+    overflow: hidden !important; /* IE need this to avoid disappearing bug with collapsed logs */
+}
+
+.useA11y .logRow:focus {
+    border-bottom: 1px solid #000000 !important;
+    outline: none !important;
+    background-color: #FFFFAD !important;
+}
+
+.useA11y .logRow:focus a.objectLink-sourceLink {
+    background-color: #FFFFAD;
+}
+
+.useA11y .a11yFocus:focus, .useA11y .objectBox:focus {
+    outline: 2px solid #FF9933;
+    background-color: #FFFFAD;
+}
+
+.useA11y .objectBox-null:focus, .useA11y .objectBox-undefined:focus{
+    background-color: #888888 !important;
+}
+
+.useA11y .logGroup.opened > .logRow {
+    border-bottom: 1px solid #ffffff;
+}
+
+.logGroup {
+    background: url(group.gif) repeat-x #FFFFFF;
+    padding: 0 !important;
+    border: none !important;
+}
+
+.logGroupBody {
+    display: none;
+    margin-left: 16px;
+    border-left: 1px solid #D7D7D7;
+    border-top: 1px solid #D7D7D7;
+    background: #FFFFFF;
+}
+
+.logGroup > .logRow {
+    background-color: transparent !important;
+    font-weight: bold;
+}
+
+.logGroup.opened > .logRow {
+    border-bottom: none;
+}
+
+.logGroup.opened > .logGroupBody {
+    display: block;
+}
+
+/*****************************************************************************************/
+
+.logRow-command > .objectBox-text {
+    font-family: Monaco, monospace;
+    color: #0000FF;
+    white-space: pre-wrap;
+}
+
+.logRow-info,
+.logRow-warn,
+.logRow-error,
+.logRow-assert,
+.logRow-warningMessage,
+.logRow-errorMessage {
+    padding-left: 22px;
+    background-repeat: no-repeat;
+    background-position: 4px 2px;
+}
+
+.logRow-assert,
+.logRow-warningMessage,
+.logRow-errorMessage {
+    padding-top: 0;
+    padding-bottom: 0;
+}
+
+.logRow-info,
+.logRow-info .objectLink-sourceLink {
+    background-color: #FFFFFF;
+}
+
+.logRow-warn,
+.logRow-warningMessage,
+.logRow-warn .objectLink-sourceLink,
+.logRow-warningMessage .objectLink-sourceLink {
+    background-color: cyan;
+}
+
+.logRow-error,
+.logRow-assert,
+.logRow-errorMessage,
+.logRow-error .objectLink-sourceLink,
+.logRow-errorMessage .objectLink-sourceLink {
+    background-color: LightYellow;
+}
+
+.logRow-error,
+.logRow-assert,
+.logRow-errorMessage {
+    color: #FF0000;
+}
+
+.logRow-info {
+    /*background-image: url(chrome://firebug/skin/infoIcon.png);*/
+}
+
+.logRow-warn,
+.logRow-warningMessage {
+    /*background-image: url(chrome://firebug/skin/warningIcon.png);*/
+}
+
+.logRow-error,
+.logRow-assert,
+.logRow-errorMessage {
+    /*background-image: url(chrome://firebug/skin/errorIcon.png);*/
+}
+
+/*****************************************************************************************/
+
+.objectBox-string,
+.objectBox-text,
+.objectBox-number,
+.objectLink-element,
+.objectLink-textNode,
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+    font-family: Monaco, monospace;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectLink-textNode {
+    white-space: pre-wrap;
+}
+
+.objectBox-number,
+.objectLink-styleRule,
+.objectLink-element,
+.objectLink-textNode {
+    color: #000088;
+}
+
+.objectBox-string {
+    color: #FF0000;
+}
+
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile  {
+    color: DarkGreen;
+}
+
+.objectBox-null,
+.objectBox-undefined {
+    padding: 0 2px;
+    border: 1px solid #666666;
+    background-color: #888888;
+    color: #FFFFFF;
+}
+
+.objectBox-exception {
+    padding: 0 2px 0 18px;
+    /*background: url(chrome://firebug/skin/errorIcon-sm.png) no-repeat 0 0;*/
+    color: red;
+}
+
+.objectLink-sourceLink {
+    position: absolute;
+    right: 4px;
+    top: 2px;
+    padding-left: 8px;
+    font-family: Lucida Grande, sans-serif;
+    font-weight: bold;
+    color: #0000FF;
+}
+
+/************************************************************************************************/
+
+.errorTitle {
+    margin-top: 0px;
+    margin-bottom: 1px;
+    padding-top: 2px;
+    padding-bottom: 2px;
+}
+
+.errorTrace {
+    margin-left: 17px;
+}
+
+.errorSourceBox {
+    margin: 2px 0;
+}
+
+.errorSource-none {
+    display: none;
+}
+
+.errorSource-syntax > .errorBreak {
+    visibility: hidden;
+}
+
+.errorSource {
+    cursor: pointer;
+    font-family: Monaco, monospace;
+    color: DarkGreen;
+}
+
+.errorSource:hover {
+    text-decoration: underline;
+}
+
+.errorBreak {
+    cursor: pointer;
+    display: none;
+    margin: 0 6px 0 0;
+    width: 13px;
+    height: 14px;
+    vertical-align: bottom;
+    /*background: url(chrome://firebug/skin/breakpoint.png) no-repeat;*/
+    opacity: 0.1;
+}
+
+.hasBreakSwitch .errorBreak {
+    display: inline;
+}
+
+.breakForError .errorBreak {
+    opacity: 1;
+}
+
+.assertDescription {
+    margin: 0;
+}
+
+/************************************************************************************************/
+
+.logRow-profile > .logRow > .objectBox-text {
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    color: #000000;
+}
+
+.logRow-profile > .logRow > .objectBox-text:last-child {
+    color: #555555;
+    font-style: italic;
+}
+
+.logRow-profile.opened > .logRow {
+    padding-bottom: 4px;
+}
+
+.profilerRunning > .logRow {
+    /*background: transparent url(chrome://firebug/skin/loading_16.gif) no-repeat 2px 0 !important;*/
+    padding-left: 22px !important;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.profileSizer {
+    width:100%;
+    overflow-x:auto;
+    overflow-y: scroll;
+}
+
+.profileTable {
+    border-bottom: 1px solid #D7D7D7;
+    padding: 0 0 4px 0;
+}
+
+.profileTable tr[odd="1"] {
+    background-color: #F5F5F5;
+    vertical-align:middle;
+}
+
+.profileTable a {
+    vertical-align:middle;
+}
+
+.profileTable td {
+    padding: 1px 4px 0 4px;
+}
+
+.headerCell {
+    cursor: pointer;
+    -moz-user-select: none;
+    border-bottom: 1px solid #9C9C9C;
+    padding: 0 !important;
+    font-weight: bold;
+    /*background: #BBBBBB url(chrome://firebug/skin/tableHeader.gif) repeat-x;*/
+}
+
+.headerCellBox {
+    padding: 2px 4px;
+    border-left: 1px solid #D9D9D9;
+    border-right: 1px solid #9C9C9C;
+}
+
+.headerCell:hover:active {
+    /*background: #959595 url(chrome://firebug/skin/tableHeaderActive.gif) repeat-x;*/
+}
+
+.headerSorted {
+    /*background: #7D93B2 url(chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;*/
+}
+
+.headerSorted > .headerCellBox {
+    border-right-color: #6B7C93;
+    /*background: url(chrome://firebug/skin/arrowDown.png) no-repeat right;*/
+}
+
+.headerSorted.sortedAscending > .headerCellBox {
+    /*background-image: url(chrome://firebug/skin/arrowUp.png);*/
+}
+
+.headerSorted:hover:active {
+    /*background: #536B90 url(chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;*/
+}
+
+.linkCell {
+    text-align: right;
+}
+
+.linkCell > .objectLink-sourceLink {
+    position: static;
+}
+
+/*****************************************************************************************/
+
+.logRow-stackTrace {
+    padding-top: 0;
+    background: #f8f8f8;
+}
+
+.logRow-stackTrace > .objectBox-stackFrame {
+    position: relative;
+    padding-top: 2px;
+}
+
+/************************************************************************************************/
+
+.objectLink-object {
+    font-family: Lucida Grande, sans-serif;
+    font-weight: bold;
+    color: DarkGreen;
+    white-space: pre-wrap;
+}
+
+/* xxxpedro reps object representation .................................... */
+.objectProp-object {
+    color: DarkGreen;
+}
+
+.objectProps {
+    color: #000;
+    font-weight: normal;
+}
+
+.objectPropName {
+    /*font-style: italic;*/
+    color: #777;
+}
+
+/*
+.objectProps .objectProp-string,
+.objectProps .objectProp-number,
+.objectProps .objectProp-object
+{
+    font-style: italic;
+}
+/**/
+
+.objectProps .objectProp-string
+{
+    /*font-family: Monaco, monospace;*/
+    color: #f55;
+}
+.objectProps .objectProp-number
+{
+    /*font-family: Monaco, monospace;*/
+    color: #55a;
+}
+.objectProps .objectProp-object
+{
+    /*font-family: Lucida Grande,sans-serif;*/
+    color: #585;
+}
+/* xxxpedro reps object representation .................................... */
+
+/************************************************************************************************/
+
+.selectorTag,
+.selectorId,
+.selectorClass {
+    font-family: Monaco, monospace;
+    font-weight: normal;
+}
+
+.selectorTag {
+    color: #0000FF;
+}
+
+.selectorId {
+    color: DarkBlue;
+}
+
+.selectorClass {
+    color: red;
+}
+
+.selectorHidden > .selectorTag {
+    color: #5F82D9;
+}
+
+.selectorHidden > .selectorId {
+    color: #888888;
+}
+
+.selectorHidden > .selectorClass {
+    color: #D86060;
+}
+
+.selectorValue {
+    font-family: Lucida Grande, sans-serif;
+    font-style: italic;
+    color: #555555;
+}
+
+/*****************************************************************************************/
+
+.panelNode.searching .logRow {
+    display: none;
+}
+
+.logRow.matched {
+    display: block !important;
+}
+
+.logRow.matching {
+    position: absolute;
+    left: -1000px;
+    top: -1000px;
+    max-width: 0;
+    max-height: 0;
+    overflow: hidden;
+}
+
+/*****************************************************************************************/
+
+.objectLeftBrace,
+.objectRightBrace,
+.objectEqual,
+.objectComma,
+.arrayLeftBracket,
+.arrayRightBracket,
+.arrayComma {
+    font-family: Monaco, monospace;
+}
+
+.objectLeftBrace,
+.objectRightBrace,
+.arrayLeftBracket,
+.arrayRightBracket {
+    font-weight: bold;
+}
+
+.objectLeftBrace,
+.arrayLeftBracket {
+    margin-right: 4px;
+}
+
+.objectRightBrace,
+.arrayRightBracket {
+    margin-left: 4px;
+}
+
+/*****************************************************************************************/
+
+.logRow-dir {
+    padding: 0;
+}
+
+/************************************************************************************************/
+
+/*
+.logRow-errorMessage > .hasTwisty > .errorTitle,
+.logRow-spy .spyHead .spyTitle,
+.logGroup > .logRow 
+*/
+.logRow-errorMessage .hasTwisty .errorTitle,
+.logRow-spy .spyHead .spyTitle,
+.logGroup .logRow {
+    cursor: pointer;
+    padding-left: 18px;
+    background-repeat: no-repeat;
+    background-position: 3px 3px;
+}
+
+.logRow-errorMessage > .hasTwisty > .errorTitle {
+    background-position: 2px 3px;
+}
+
+.logRow-errorMessage > .hasTwisty > .errorTitle:hover,
+.logRow-spy .spyHead .spyTitle:hover,
+.logGroup > .logRow:hover {
+    text-decoration: underline;
+}
+
+/*****************************************************************************************/
+
+.logRow-spy {
+    padding: 0 !important;
+}
+
+.logRow-spy,
+.logRow-spy .objectLink-sourceLink {
+    background: url(group.gif) repeat-x #FFFFFF;
+    padding-right: 4px;
+    right: 0;
+}
+
+.logRow-spy.opened {
+    padding-bottom: 4px;
+    border-bottom: none;
+}
+
+.spyTitle {
+    color: #000000;
+    font-weight: bold;
+    -moz-box-sizing: padding-box;
+    overflow: hidden;
+    z-index: 100;
+    padding-left: 18px;
+}
+
+.spyCol {
+    padding: 0;
+    white-space: nowrap;
+    height: 16px;
+}
+
+.spyTitleCol:hover > .objectLink-sourceLink,
+.spyTitleCol:hover > .spyTime,
+.spyTitleCol:hover > .spyStatus,
+.spyTitleCol:hover > .spyTitle {
+    display: none;
+}
+
+.spyFullTitle {
+    display: none;
+    -moz-user-select: none;
+    max-width: 100%;
+    background-color: Transparent;
+}
+
+.spyTitleCol:hover > .spyFullTitle {
+    display: block;
+}
+
+.spyStatus {
+    padding-left: 10px;
+    color: rgb(128, 128, 128);
+}
+
+.spyTime {
+    margin-left:4px;
+    margin-right:4px;
+    color: rgb(128, 128, 128);
+}
+
+.spyIcon {
+    margin-right: 4px;
+    margin-left: 4px;
+    width: 16px;
+    height: 16px;
+    vertical-align: middle;
+    background: transparent no-repeat 0 0;
+    display: none;
+}
+
+.loading .spyHead .spyRow .spyIcon {
+    background-image: url(loading_16.gif);
+    display: block;
+}
+
+.logRow-spy.loaded:not(.error) .spyHead .spyRow .spyIcon {
+    width: 0;
+    margin: 0;
+}
+
+.logRow-spy.error .spyHead .spyRow .spyIcon {
+    background-image: url(errorIcon-sm.png);
+    display: block;
+    background-position: 2px 2px;
+}
+
+.logRow-spy .spyHead .netInfoBody {
+    display: none;
+}
+
+.logRow-spy.opened .spyHead .netInfoBody {
+    margin-top: 10px;
+    display: block;
+}
+
+.logRow-spy.error .spyTitle,
+.logRow-spy.error .spyStatus,
+.logRow-spy.error .spyTime {
+    color: red;
+}
+
+.logRow-spy.loading .spyResponseText {
+    font-style: italic;
+    color: #888888;
+}
+
+/************************************************************************************************/
+
+.caption {
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-weight: bold;
+    color:  #444444;
+}
+
+.warning {
+    padding: 10px;
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-weight: bold;
+    color:  #888888;
+}
+
+
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* DOM */
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+
+
+/* See license.txt for terms of usage */
+
+.panelNode-dom {
+    overflow-x: hidden !important;
+}
+
+.domTable {
+    font-size: 1em;
+    width: 100%;
+    table-layout: fixed;
+    background: #fff;
+}
+
+.domTableIE {
+    width: auto;
+}
+
+.memberLabelCell {
+    padding: 2px 0 2px 0;
+    vertical-align: top;
+}
+
+.memberValueCell {
+    padding: 1px 0 1px 5px;
+    display: block;
+    overflow: hidden;
+}
+
+.memberLabel {
+    display: block;
+    cursor: default;
+    -moz-user-select:  none;
+    overflow: hidden;
+    /*position: absolute;*/
+    padding-left: 18px;
+    /*max-width: 30%;*/
+    /*white-space: nowrap;*/
+    background-color: #FFFFFF;
+    text-decoration: none;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.memberRow.hasChildren .memberLabelCell .memberLabel:hover {
+    cursor: pointer;
+    color: blue;
+    text-decoration: underline;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.userLabel {
+    color: #000000;
+    font-weight: bold;
+}
+
+.userClassLabel {
+    color: #E90000;
+    font-weight: bold;
+}
+
+.userFunctionLabel {
+    color: #025E2A;
+    font-weight: bold;
+}
+
+.domLabel {
+    color: #000000;
+}
+
+.domFunctionLabel {
+    color: #025E2A;
+}
+
+.ordinalLabel {
+    color: SlateBlue;
+    font-weight: bold;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+.scopesRow {
+    padding: 2px 18px;
+    background-color: LightYellow;
+    border-bottom: 5px solid #BEBEBE;
+    color: #666666;
+}
+.scopesLabel {
+    background-color:  LightYellow;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.watchEditCell {
+    padding: 2px 18px;
+    background-color: LightYellow;
+    border-bottom: 1px solid #BEBEBE;
+    color: #666666;
+}
+
+.editor-watchNewRow,
+.editor-memberRow {
+    font-family: Monaco, monospace !important;
+}
+
+.editor-memberRow {
+    padding: 1px 0 !important;
+}
+
+.editor-watchRow {
+    padding-bottom: 0 !important;
+}
+
+.watchRow > .memberLabelCell {
+    font-family: Monaco, monospace;
+    padding-top: 1px;
+    padding-bottom: 1px;
+}
+
+.watchRow > .memberLabelCell > .memberLabel {
+    background-color: transparent;
+}
+
+.watchRow > .memberValueCell {
+    padding-top: 2px;
+    padding-bottom: 2px;
+}
+
+.watchRow > .memberLabelCell,
+.watchRow > .memberValueCell {
+    background-color: #F5F5F5;
+    border-bottom: 1px solid #BEBEBE;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.watchToolbox {
+    z-index: 2147483647;
+    position: absolute;
+    right: 0;
+    padding: 1px 2px;
+}
+
+
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/*************************************************************************************************/
+/* FROM ORIGINAL FIREBUG */
+
+
+
+
+/************************************************************************************************
+ CSS Not organized
+*************************************************************************************************/
+#fbConsole {
+    overflow-x: hidden !important;
+}
+
+#fbCSS {
+    font: 1em Monaco, monospace;
+    padding: 0 7px;
+}
+
+#fbstylesheetButtons select, #fbScriptButtons select {
+    font: 11px Lucida Grande, Tahoma, sans-serif;
+    margin-top: 1px;
+    padding-left: 3px;
+    background: #fafafa;
+    border: 1px inset #fff;
+    width: 220px;
+    outline: none;
+}
+
+.Selector { margin-top:10px }
+.CSSItem {margin-left: 4% }
+.CSSText { padding-left:20px; }
+.CSSProperty { color:#005500; }
+.CSSValue { padding-left:5px; color:#000088; }
+
+
+/************************************************************************************************
+ Not organized
+*************************************************************************************************/
+
+#fbHTMLStatusBar {
+    display: inline;
+}
+
+.fbToolbarButtons {
+    display: none;
+}
+
+.fbStatusSeparator{
+    display: block;
+    float: left;
+    padding-top: 4px;
+}
+
+#fbStatusBarBox {
+    display: none;
+}
+
+#fbToolbarContent {
+    display: block;
+    position: absolute;
+    _position: absolute;
+    top: 0;
+    padding-top: 4px;
+    height: 23px;
+    clip: rect(0, 2048px, 27px, 0);
+}
+
+.fbTabMenuTarget {
+    display: none !important;
+    float: left;
+    width: 10px;
+    height: 10px;
+    margin-top: 6px;
+    background: url(tabMenuTarget.png);   
+}
+
+.fbTabMenuTarget:hover {
+    background: url(tabMenuTargetHover.png);   
+}
+
+.fbShadow {
+    float: left;
+    background: url(shadowAlpha.png) no-repeat bottom right !important;
+    background: url(shadow2.gif) no-repeat bottom right;
+    margin: 10px 0 0 10px !important;
+    margin: 10px 0 0 5px;
+}
+
+.fbShadowContent {
+    display: block;
+    position: relative;
+    background-color: #fff;
+    border: 1px solid #a9a9a9;
+    top: -6px;
+    left: -6px;
+}
+
+.fbMenu {
+    display: none;
+    position: absolute;
+    font-size: 11px;
+    line-height: 13px;
+    z-index: 2147483647;
+}
+
+.fbMenuContent {
+    padding: 2px;
+}
+
+.fbMenuSeparator {
+    display: block;
+    position: relative;
+    padding: 1px 18px 0;
+    text-decoration: none;
+    color: #000;
+    cursor: default;    
+    background: #ACA899;
+    margin: 4px 0;
+}
+
+.fbMenuOption
+{
+    display: block;
+    position: relative;
+    padding: 2px 18px;
+    text-decoration: none;
+    color: #000;
+    cursor: default;
+}
+
+.fbMenuOption:hover
+{
+    color: #fff;
+    background: #316AC5;
+}
+
+.fbMenuGroup {
+    background: transparent url(tabMenuPin.png) no-repeat right 0;
+}
+
+.fbMenuGroup:hover {
+    background: #316AC5 url(tabMenuPin.png) no-repeat right -17px;
+}
+
+.fbMenuGroupSelected {
+    color: #fff;
+    background: #316AC5 url(tabMenuPin.png) no-repeat right -17px;
+}
+
+.fbMenuChecked  {
+    background: transparent url(tabMenuCheckbox.png) no-repeat 4px 0;
+}
+
+.fbMenuChecked:hover {
+    background: #316AC5 url(tabMenuCheckbox.png) no-repeat 4px -17px;
+}
+
+.fbMenuRadioSelected {
+    background: transparent url(tabMenuRadio.png) no-repeat 4px 0;
+}
+
+.fbMenuRadioSelected:hover {
+    background: #316AC5 url(tabMenuRadio.png) no-repeat 4px -17px;
+}
+
+.fbMenuShortcut {
+    padding-right: 85px; 
+}
+
+.fbMenuShortcutKey {
+    position: absolute;
+    right: 0;
+    top: 2px;
+    width: 77px;
+}
+
+#fbFirebugMenu {
+    top: 22px;
+    left: 0;
+}
+
+.fbMenuDisabled {
+    color: #ACA899 !important;
+}
+
+#fbFirebugSettingsMenu {
+    left: 245px;
+    top: 99px;
+}
+
+#fbConsoleMenu {
+    top: 42px;
+    left: 48px;
+}
+
+.fbIconButton {
+    display: block;
+}
+
+.fbIconButton {
+    display: block;
+}
+
+.fbIconButton {
+    display: block;
+    float: left;
+    height: 20px;
+    width: 20px;
+    color: #000;
+    margin-right: 2px;
+    text-decoration: none;
+    cursor: default;
+}
+
+.fbIconButton:hover {
+    position: relative;
+    top: -1px;
+    left: -1px;
+    margin-right: 0;
+    _margin-right: 1px;
+    color: #333;
+    border: 1px solid #fff;
+    border-bottom: 1px solid #bbb;
+    border-right: 1px solid #bbb;
+}
+
+.fbIconPressed {
+    position: relative;
+    margin-right: 0;
+    _margin-right: 1px;
+    top: 0 !important;
+    left: 0 !important;
+    height: 19px;
+    color: #333 !important;
+    border: 1px solid #bbb !important;
+    border-bottom: 1px solid #cfcfcf !important;
+    border-right: 1px solid #ddd !important;
+}
+
+
+
+/************************************************************************************************
+ Error Popup
+*************************************************************************************************/
+#fbErrorPopup {
+    position: absolute;
+    right: 0;
+    bottom: 0;
+    height: 19px;
+    width: 75px;
+    background: url(sprite.png) #f1f2ee 0 0;
+    z-index: 999;
+}
+
+#fbErrorPopupContent {
+    position: absolute;
+    right: 0;
+    top: 1px;
+    height: 18px;
+    width: 75px;
+    _width: 74px;
+    border-left: 1px solid #aca899;
+}
+
+#fbErrorIndicator {
+    position: absolute;
+    top: 2px;
+    right: 5px;
+}
+
+
+
+
+
+
+
+
+
+
+.fbBtnInspectActive {
+    background: #aaa;
+    color: #fff !important;
+}
+
+/************************************************************************************************
+ General
+*************************************************************************************************/
+.fbBody {
+    margin: 0;
+    padding: 0;
+    overflow: hidden;
+    
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-size: 11px;
+    background: #fff;
+}
+
+.clear {
+    clear: both;
+}
+
+/************************************************************************************************
+ Mini Chrome
+*************************************************************************************************/
+#fbMiniChrome {
+    display: none;
+    right: 0;
+    height: 27px;
+    background: url(sprite.png) #f1f2ee 0 0;
+    margin-left: 1px;
+}
+
+#fbMiniContent {
+    display: block;
+    position: relative;
+    left: -1px;
+    right: 0;
+    top: 1px;
+    height: 25px;
+    border-left: 1px solid #aca899;
+}
+
+#fbToolbarSearch {
+    float: right;
+    border: 1px solid #ccc;
+    margin: 0 5px 0 0;
+    background: #fff url(search.png) no-repeat 4px 2px !important;
+    background: #fff url(search.gif) no-repeat 4px 2px;
+    padding-left: 20px;    
+    font-size: 11px;
+}
+
+#fbToolbarErrors {
+    float: right;
+    margin: 1px 4px 0 0;
+    font-size: 11px;
+}
+
+#fbLeftToolbarErrors {
+    float: left;
+    margin: 7px 0px 0 5px;
+    font-size: 11px;
+}
+
+.fbErrors {
+    padding-left: 20px;
+    height: 14px;
+    background: url(errorIcon.png) no-repeat !important;
+    background: url(errorIcon.gif) no-repeat;
+    color: #f00;
+    font-weight: bold;    
+}
+
+#fbMiniErrors {
+    display: inline;
+    display: none;
+    float: right;
+    margin: 5px 2px 0 5px;
+}
+
+#fbMiniIcon {
+    float: right;
+    margin: 3px 4px 0;
+    height: 20px;
+    width: 20px;
+    float: right;    
+    background: url(sprite.png) 0 -135px;
+    cursor: pointer;
+}
+
+
+/************************************************************************************************
+ Master Layout
+*************************************************************************************************/
+#fbChrome {
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-size: 11px;
+    position: absolute;
+    _position: static;
+    top: 0;
+    left: 0;
+    height: 100%;
+    width: 100%;
+    border-collapse: collapse;
+    border-spacing: 0;
+    background: #fff;
+    overflow: hidden;
+}
+
+#fbChrome > tbody > tr > td {
+    padding: 0;
+}
+
+#fbTop {
+    height: 49px;
+}
+
+#fbToolbar {
+    background: url(sprite.png) #f1f2ee 0 0;
+    height: 27px;
+    font-size: 11px;
+    line-height: 13px;
+}
+
+#fbPanelBarBox {
+    background: url(sprite.png) #dbd9c9 0 -27px;
+    height: 22px;
+}
+
+#fbContent {
+    height: 100%;
+    vertical-align: top;
+}
+
+#fbBottom {
+    height: 18px;
+    background: #fff;
+}
+
+/************************************************************************************************
+ Sub-Layout 
+*************************************************************************************************/
+
+/* fbToolbar 
+*************************************************************************************************/
+#fbToolbarIcon {
+    float: left;
+    padding: 0 5px 0;
+}
+
+#fbToolbarIcon a {
+    background: url(sprite.png) 0 -135px;
+}
+
+#fbToolbarButtons {
+    padding: 0 2px 0 5px;
+}
+
+#fbToolbarButtons {
+    padding: 0 2px 0 5px;
+}
+/*
+#fbStatusBarBox a {
+    text-decoration: none;
+    display: block;
+    float: left;
+    color: #000;
+    padding: 4px 5px;
+    margin: 0 0 0 1px;
+    cursor: default;
+}
+
+#fbStatusBarBox a:hover {
+    color: #333;
+    padding: 3px 4px;
+    border: 1px solid #fff;
+    border-bottom: 1px solid #bbb;
+    border-right: 1px solid #bbb;
+}
+/**/
+
+.fbButton {
+    text-decoration: none;
+    display: block;
+    float: left;
+    color: #000;
+    padding: 4px 6px 4px 7px;
+    cursor: default;
+}
+
+.fbButton:hover {
+    color: #333;
+    background: #f5f5ef url(buttonBg.png);
+    padding: 3px 5px 3px 6px;
+    border: 1px solid #fff;
+    border-bottom: 1px solid #bbb;
+    border-right: 1px solid #bbb;
+}
+
+.fbBtnPressed {
+    background: #e3e3db url(buttonBgHover.png) !important;
+    padding: 3px 4px 2px 6px !important;
+    margin: 1px 0 0 1px !important;
+    border: 1px solid #ACA899 !important;
+    border-color: #ACA899 #ECEBE3 #ECEBE3 #ACA899 !important;
+}
+
+#fbStatusBarBox {
+    top: 4px;
+    cursor: default;    
+}
+
+.fbToolbarSeparator {
+    overflow: hidden;
+    border: 1px solid;
+    border-color: transparent #fff transparent #777;
+    _border-color: #eee #fff #eee #777;
+    height: 7px;
+    margin: 6px 3px;
+    float: left;
+}
+
+.fbBtnSelected {
+    font-weight: bold;
+}
+
+.fbStatusBar {
+    color: #aca899;
+}
+
+.fbStatusBar a {
+    text-decoration: none;
+    color: black;
+}
+
+.fbStatusBar a:hover {
+    color: blue;
+    cursor: pointer;    
+}
+
+
+#fbWindowButtons {
+    position: absolute;
+    white-space: nowrap;
+    right: 0;
+    top: 0;
+    height: 17px;
+    width: 48px;
+    padding: 5px;
+    z-index: 6;
+    background: url(sprite.png) #f1f2ee 0 0;
+}
+
+/* fbPanelBarBox
+*************************************************************************************************/
+
+#fbPanelBar1 {
+    width: 1024px; /* fixed width to avoid tabs breaking line */
+    z-index: 8;
+    left: 0;
+    white-space: nowrap;
+    background: url(sprite.png) #dbd9c9 0 -27px;
+    position: absolute;
+    left: 4px;
+}
+
+#fbPanelBar2Box {
+    background: url(sprite.png) #dbd9c9 0 -27px;
+    position: absolute;
+    height: 22px;
+    width: 300px; /* fixed width to avoid tabs breaking line */
+    z-index: 9;
+    right: 0;
+}
+
+#fbPanelBar2 {
+    position: absolute;
+    width: 290px; /* fixed width to avoid tabs breaking line */
+    height: 22px;
+    padding-left: 4px;
+}
+
+/* body 
+*************************************************************************************************/
+.fbPanel {
+    display: none;
+}
+
+#fbPanelBox1, #fbPanelBox2 {
+    max-height: inherit;
+    height: 100%;
+    font-size: 1em;
+}
+
+#fbPanelBox2 {
+    background: #fff;
+}
+
+#fbPanelBox2 {
+    width: 300px;
+    background: #fff;
+}
+
+#fbPanel2 {
+    margin-left: 6px;
+    background: #fff;
+}
+
+#fbLargeCommandLine {
+    display: none;
+    position: absolute;
+    z-index: 9;
+    top: 27px;
+    right: 0;
+    width: 294px;
+    height: 201px;
+    border-width: 0;
+    margin: 0;
+    padding: 2px 0 0 2px;
+    resize: none;
+    outline: none;
+    font-size: 11px;
+    overflow: auto;
+    border-top: 1px solid #B9B7AF;
+    _right: -1px;
+    _border-left: 1px solid #fff;
+}
+
+#fbLargeCommandButtons {
+    display: none;
+    background: #ECE9D8;
+    bottom: 0;
+    right: 0;
+    width: 294px;
+    height: 21px;
+    padding-top: 1px;
+    position: fixed;
+    border-top: 1px solid #ACA899;
+    z-index: 9;
+}
+
+#fbSmallCommandLineIcon {
+    background: url(down.png) no-repeat;
+    position: absolute;
+    right: 2px;
+    bottom: 3px;
+    
+    z-index: 99;
+}
+
+#fbSmallCommandLineIcon:hover {
+    background: url(downHover.png) no-repeat;
+}
+
+.hide {
+    overflow: hidden !important;
+    position: fixed !important;
+    display: none !important;
+    visibility: hidden !important;
+}
+
+/* fbBottom 
+*************************************************************************************************/
+
+#fbCommand {
+    height: 18px;
+}
+
+#fbCommandBox {
+    position: fixed;
+    _position: absolute;
+    width: 100%;
+    height: 18px;
+    bottom: 0;
+    overflow: hidden;
+    z-index: 9;
+    background: #fff;
+    border: 0;
+    border-top: 1px solid #ccc;
+}
+
+#fbCommandIcon {
+    position: absolute;
+    color: #00f;
+    top: 2px;
+    left: 6px;
+    display: inline;
+    font: 11px Monaco, monospace;
+    z-index: 10;
+}
+
+#fbCommandLine {
+    position: absolute;
+    width: 100%;
+    top: 0;
+    left: 0;
+    border: 0;
+    margin: 0;
+    padding: 2px 0 2px 32px;
+    font: 11px Monaco, monospace;
+    z-index: 9;
+    outline: none;
+}
+
+#fbLargeCommandLineIcon {
+    background: url(up.png) no-repeat;
+    position: absolute;
+    right: 1px;
+    bottom: 1px;
+    z-index: 10;
+}
+
+#fbLargeCommandLineIcon:hover {
+    background: url(upHover.png) no-repeat;
+}
+
+div.fbFitHeight {
+    overflow: auto;
+    position: relative;
+}
+
+
+/************************************************************************************************
+ Layout Controls
+*************************************************************************************************/
+
+/* fbToolbar buttons 
+*************************************************************************************************/
+.fbSmallButton {
+    overflow: hidden;
+    width: 16px;
+    height: 16px;
+    display: block;
+    text-decoration: none;
+    cursor: default;
+}
+
+#fbWindowButtons .fbSmallButton {
+    float: right;
+}
+
+#fbWindow_btClose {
+    background: url(min.png);
+}
+
+#fbWindow_btClose:hover {
+    background: url(minHover.png);
+}
+
+#fbWindow_btDetach {
+    background: url(detach.png);
+}
+
+#fbWindow_btDetach:hover {
+    background: url(detachHover.png);
+}
+
+#fbWindow_btDeactivate {
+    background: url(off.png);
+}
+
+#fbWindow_btDeactivate:hover {
+    background: url(offHover.png);
+}
+
+
+/* fbPanelBarBox tabs 
+*************************************************************************************************/
+.fbTab {
+    text-decoration: none;
+    display: none;
+    float: left;
+    width: auto;
+    float: left;
+    cursor: default;
+    font-family: Lucida Grande, Tahoma, sans-serif;
+    font-size: 11px;
+    line-height: 13px;
+    font-weight: bold;
+    height: 22px;
+    color: #565656;
+}
+
+.fbPanelBar span {
+    /*display: block; TODO: safe to remove this? */
+    float: left;
+}
+
+.fbPanelBar .fbTabL,.fbPanelBar .fbTabR {
+    height: 22px;
+    width: 8px;
+}
+
+.fbPanelBar .fbTabText {
+    padding: 4px 1px 0;
+}
+
+a.fbTab:hover {
+    background: url(sprite.png) 0 -73px;
+}
+
+a.fbTab:hover .fbTabL {
+    background: url(sprite.png) -16px -96px;
+}
+
+a.fbTab:hover .fbTabR {
+    background: url(sprite.png) -24px -96px;
+}
+
+.fbSelectedTab {
+    background: url(sprite.png) #f1f2ee 0 -50px !important;
+    color: #000;
+}
+
+.fbSelectedTab .fbTabL {
+    background: url(sprite.png) 0 -96px !important;
+}
+
+.fbSelectedTab .fbTabR {
+    background: url(sprite.png) -8px -96px !important;
+}
+
+/* splitters 
+*************************************************************************************************/
+#fbHSplitter {
+    position: fixed;
+    _position: absolute;
+    left: 0;
+    top: 0;
+    width: 100%;
+    height: 5px;
+    overflow: hidden;
+    cursor: n-resize !important;
+    background: url(pixel_transparent.gif);
+    z-index: 9;
+}
+
+#fbHSplitter.fbOnMovingHSplitter {
+    height: 100%;
+    z-index: 100;
+}
+
+.fbVSplitter {
+    background: #ece9d8;
+    color: #000;
+    border: 1px solid #716f64;
+    border-width: 0 1px;
+    border-left-color: #aca899;
+    width: 4px;
+    cursor: e-resize;
+    overflow: hidden;
+    right: 294px;
+    text-decoration: none;
+    z-index: 10;
+    position: absolute;
+    height: 100%;
+    top: 27px;
+}
+
+/************************************************************************************************/
+div.lineNo {
+    font: 1em/1.4545em Monaco, monospace;
+    position: relative;
+    float: left;
+    top: 0;
+    left: 0;
+    margin: 0 5px 0 0;
+    padding: 0 5px 0 10px;
+    background: #eee;
+    color: #888;
+    border-right: 1px solid #ccc;
+    text-align: right;
+}
+
+.sourceBox {
+    position: absolute;
+}
+
+.sourceCode {
+    font: 1em Monaco, monospace;
+    overflow: hidden;
+    white-space: pre;
+    display: inline;
+}
+
+/************************************************************************************************/
+.nodeControl {
+    margin-top: 3px;
+    margin-left: -14px;
+    float: left;
+    width: 9px;
+    height: 9px;
+    overflow: hidden;
+    cursor: default;
+    background: url(tree_open.gif);
+    _float: none;
+    _display: inline;
+    _position: absolute;
+}
+
+div.nodeMaximized {
+    background: url(tree_close.gif);
+}
+
+div.objectBox-element {
+    padding: 1px 3px;
+}
+.objectBox-selector{
+    cursor: default;
+}
+
+.selectedElement{
+    background: highlight;
+    /* background: url(roundCorner.svg); Opera */
+    color: #fff !important;
+}
+.selectedElement span{
+    color: #fff !important;
+}
+
+/* IE6 need this hack */
+* html .selectedElement {
+    position: relative;
+}
+
+/* Webkit CSS Hack - bug in "highlight" named color */ 
+@media screen and (-webkit-min-device-pixel-ratio:0) {
+    .selectedElement{
+      background: #316AC5;
+      color: #fff !important;
+    }
+}
+
+/************************************************************************************************/
+/************************************************************************************************/
+.logRow * {
+    font-size: 1em;
+}
+
+/* TODO: remove this? */
+/* TODO: xxxpedro - IE need this in windowless mode (cnn.com) check if the issue is related to 
+position. if so, override it at chrome.js initialization when creating the div */
+.logRow {
+    position: relative;
+    border-bottom: 1px solid #D7D7D7;
+    padding: 2px 4px 1px 6px;
+    zbackground-color: #FFFFFF;
+}
+/**/
+
+.logRow-command {
+    font-family: Monaco, monospace;
+    color: blue;
+}
+
+.objectBox-string,
+.objectBox-text,
+.objectBox-number,
+.objectBox-function,
+.objectLink-element,
+.objectLink-textNode,
+.objectLink-function,
+.objectBox-stackTrace,
+.objectLink-profile {
+    font-family: Monaco, monospace;
+}
+
+.objectBox-null {
+    padding: 0 2px;
+    border: 1px solid #666666;
+    background-color: #888888;
+    color: #FFFFFF;
+}
+
+.objectBox-string {
+    color: red;
+    
+    /* TODO: xxxpedro make long strings break line */
+    /*white-space: pre; */ 
+}
+
+.objectBox-number {
+    color: #000088;
+}
+
+.objectBox-function {
+    color: DarkGreen;
+}
+
+.objectBox-object {
+    color: DarkGreen;
+    font-weight: bold;
+    font-family: Lucida Grande, sans-serif;
+}
+
+.objectBox-array {
+    color: #000;
+}
+
+/************************************************************************************************/
+.logRow-info,.logRow-error,.logRow-warn {
+    background: #fff no-repeat 2px 2px;
+    padding-left: 20px;
+    padding-bottom: 3px;
+}
+
+.logRow-info {
+    background-image: url(infoIcon.png) !important;
+    background-image: url(infoIcon.gif);
+}
+
+.logRow-warn {
+    background-color: cyan;
+    background-image: url(warningIcon.png) !important;
+    background-image: url(warningIcon.gif);
+}
+
+.logRow-error {
+    background-color: LightYellow;
+    background-image: url(errorIcon.png) !important;
+    background-image: url(errorIcon.gif);
+    color: #f00;
+}
+
+.errorMessage {
+    vertical-align: top;
+    color: #f00;
+}
+
+.objectBox-sourceLink {
+    position: absolute;
+    right: 4px;
+    top: 2px;
+    padding-left: 8px;
+    font-family: Lucida Grande, sans-serif;
+    font-weight: bold;
+    color: #0000FF;
+}
+
+/************************************************************************************************/
+/*
+//TODO: remove this when console2 is finished
+*/
+/*
+.logRow-group {
+    background: #EEEEEE;
+    border-bottom: none;
+}
+
+.logGroup {
+    background: #EEEEEE;
+}
+
+.logGroupBox {
+    margin-left: 24px;
+    border-top: 1px solid #D7D7D7;
+    border-left: 1px solid #D7D7D7;
+}/**/
+
+/************************************************************************************************/
+.selectorTag,.selectorId,.selectorClass {
+    font-family: Monaco, monospace;
+    font-weight: normal;
+}
+
+.selectorTag {
+    color: #0000FF;
+}
+
+.selectorId {
+    color: DarkBlue;
+}
+
+.selectorClass {
+    color: red;
+}
+
+/************************************************************************************************/
+.objectBox-element {
+    font-family: Monaco, monospace;
+    color: #000088;
+}
+
+.nodeChildren {
+    padding-left: 26px;
+}
+
+.nodeTag {
+    color: blue;
+    cursor: pointer;
+}
+
+.nodeValue {
+    color: #FF0000;
+    font-weight: normal;
+}
+
+.nodeText,.nodeComment {
+    margin: 0 2px;
+    vertical-align: top;
+}
+
+.nodeText {
+    color: #333333;
+    font-family: Monaco, monospace;
+}
+
+.nodeComment {
+    color: DarkGreen;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeHidden, .nodeHidden * {
+    color: #888888;
+}
+
+.nodeHidden .nodeTag {
+    color: #5F82D9;
+}
+
+.nodeHidden .nodeValue {
+    color: #D86060;
+}
+
+.selectedElement .nodeHidden, .selectedElement .nodeHidden * {
+    color: SkyBlue !important;
+}
+
+
+/************************************************************************************************/
+.log-object {
+    /*
+    _position: relative;
+    _height: 100%;
+    /**/
+}
+
+.property {
+    position: relative;
+    clear: both;
+    height: 15px;
+}
+
+.propertyNameCell {
+    vertical-align: top;
+    float: left;
+    width: 28%;
+    position: absolute;
+    left: 0;
+    z-index: 0;
+}
+
+.propertyValueCell {
+    float: right;
+    width: 68%;
+    background: #fff;
+    position: absolute;
+    padding-left: 5px;
+    display: table-cell;
+    right: 0;
+    z-index: 1;
+    /*
+    _position: relative;
+    /**/
+}
+
+.propertyName {
+    font-weight: bold;
+}
+
+.FirebugPopup {
+    height: 100% !important;
+}
+
+.FirebugPopup #fbWindowButtons {
+    display: none !important;
+}
+
+.FirebugPopup #fbHSplitter {
+    display: none !important;
+}
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html
new file mode 100644
index 0000000..aa07809
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.html
@@ -0,0 +1,215 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/DTD/strict.dtd">
+<html>
+<head>
+<meta http-equiv="content-type" content="text/html; charset=utf-8">
+<title>Firebug Lite</title>
+<!-- An empty script to avoid FOUC when loading the stylesheet -->
+<script type="text/javascript"></script>
+<style type="text/css" media="screen">@import "firebug.css";</style>
+<style>html,body{margin:0;padding:0;overflow:hidden;}</style>
+</head>
+<body class="fbBody">
+<table id="fbChrome" cellpadding="0" cellspacing="0" border="0">
+  <tbody>
+    <tr>
+      <!-- Interface - Top Area -->
+      <td id="fbTop" colspan="2">
+      
+        <!-- 
+        <div>
+          --><!-- <span id="fbToolbarErrors" class="fbErrors">2 errors</span> --><!-- 
+          <input type="text" id="fbToolbarSearch" />
+        </div>
+        -->
+              
+        <!-- Window Buttons -->
+        <div id="fbWindowButtons">
+          <a id="fbWindow_btDeactivate" class="fbSmallButton fbHover" title="Deactivate Firebug for this web page">&nbsp;</a>
+          <a id="fbWindow_btDetach" class="fbSmallButton fbHover" title="Open Firebug in popup window">&nbsp;</a>
+          <a id="fbWindow_btClose" class="fbSmallButton fbHover" title="Minimize Firebug">&nbsp;</a>
+        </div>
+        
+        <!-- Toolbar buttons and Status Bar -->
+        <div id="fbToolbar">
+          <div id="fbToolbarContent">
+        
+          <!-- Firebug Button -->
+          <span id="fbToolbarIcon">
+            <a id="fbFirebugButton" class="fbIconButton" class="fbHover" target="_blank">&nbsp;</a>
+          </span>
+          
+          <!-- 
+          <span id="fbLeftToolbarErrors" class="fbErrors">2 errors</span>
+           -->
+           
+          <!-- Toolbar Buttons -->
+          <span id="fbToolbarButtons">
+            <!-- Fixed Toolbar Buttons -->
+            <span id="fbFixedButtons">
+                <a id="fbChrome_btInspect" class="fbButton fbHover" title="Click an element in the page to inspect">Inspect</a>
+            </span>
+            
+            <!-- Console Panel Toolbar Buttons -->
+            <span id="fbConsoleButtons" class="fbToolbarButtons">
+              <a id="fbConsole_btClear" class="fbButton fbHover" title="Clear the console">Clear</a>
+            </span>
+            
+            <!-- HTML Panel Toolbar Buttons -->
+            <!-- 
+            <span id="fbHTMLButtons" class="fbToolbarButtons">
+              <a id="fbHTML_btEdit" class="fbHover" title="Edit this HTML">Edit</a>
+            </span>
+             -->
+          </span>
+          
+          <!-- Status Bar -->
+          <span id="fbStatusBarBox">
+            <span class="fbToolbarSeparator"></span>
+            <!-- HTML Panel Status Bar -->
+            <!-- 
+            <span id="fbHTMLStatusBar" class="fbStatusBar fbToolbarButtons">
+            </span>
+             -->
+          </span>
+          
+          </div>
+          
+        </div>
+        
+        <!-- PanelBars -->
+        <div id="fbPanelBarBox">
+        
+          <!-- Main PanelBar -->
+          <div id="fbPanelBar1" class="fbPanelBar">
+            <a id="fbConsoleTab" class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">Console</span>
+                <span class="fbTabMenuTarget"></span>
+                <span class="fbTabR"></span>
+            </a>
+            <a id="fbHTMLTab" class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">HTML</span>
+                <span class="fbTabR"></span>
+            </a>
+            <a class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">CSS</span>
+                <span class="fbTabR"></span>
+            </a>
+            <a class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">Script</span>
+                <span class="fbTabR"></span>
+            </a>
+            <a class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">DOM</span>
+                <span class="fbTabR"></span>
+            </a>
+          </div>
+
+          <!-- Side PanelBars -->
+          <div id="fbPanelBar2Box" class="hide">
+            <div id="fbPanelBar2" class="fbPanelBar">
+            <!-- 
+              <a class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">Style</span>
+                <span class="fbTabR"></span>
+              </a>
+              <a class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">Layout</span>
+                <span class="fbTabR"></span>
+              </a>
+              <a class="fbTab fbHover">
+                <span class="fbTabL"></span>
+                <span class="fbTabText">DOM</span>
+                <span class="fbTabR"></span>
+              </a>
+           -->
+            </div>
+          </div>
+          
+        </div>
+        
+        <!-- Horizontal Splitter -->
+        <div id="fbHSplitter">&nbsp;</div>
+        
+      </td>
+    </tr>
+    
+    <!-- Interface - Main Area -->
+    <tr id="fbContent">
+    
+      <!-- Panels  -->
+      <td id="fbPanelBox1">
+        <div id="fbPanel1" class="fbFitHeight">
+          <div id="fbConsole" class="fbPanel"></div>
+          <div id="fbHTML" class="fbPanel"></div>
+        </div>
+      </td>
+      
+      <!-- Side Panel Box -->
+      <td id="fbPanelBox2" class="hide">
+      
+        <!-- VerticalSplitter -->
+        <div id="fbVSplitter" class="fbVSplitter">&nbsp;</div>
+        
+        <!-- Side Panels -->
+        <div id="fbPanel2" class="fbFitHeight">
+        
+          <!-- HTML Side Panels -->
+          <div id="fbHTML_Style" class="fbPanel"></div>
+          <div id="fbHTML_Layout" class="fbPanel"></div>
+          <div id="fbHTML_DOM" class="fbPanel"></div>
+          
+        </div>
+        
+        <!-- Large Command Line -->
+        <textarea id="fbLargeCommandLine" class="fbFitHeight"></textarea>
+        
+        <!-- Large Command Line Buttons -->
+        <div id="fbLargeCommandButtons">
+            <a id="fbCommand_btRun" class="fbButton fbHover">Run</a>
+            <a id="fbCommand_btClear" class="fbButton fbHover">Clear</a>
+            
+            <a id="fbSmallCommandLineIcon" class="fbSmallButton fbHover"></a>
+        </div>
+        
+      </td>
+      
+    </tr>
+    
+    <!-- Interface - Bottom Area -->
+    <tr id="fbBottom" class="hide">
+    
+      <!-- Command Line -->
+      <td id="fbCommand" colspan="2">
+        <div id="fbCommandBox">
+          <div id="fbCommandIcon">&gt;&gt;&gt;</div>
+          <input id="fbCommandLine" name="fbCommandLine" type="text" />
+          <a id="fbLargeCommandLineIcon" class="fbSmallButton fbHover"></a>
+        </div>
+      </td>
+      
+    </tr>
+    
+  </tbody>
+</table> 
+<span id="fbMiniChrome">
+  <span id="fbMiniContent">
+    <span id="fbMiniIcon" title="Open Firebug Lite"></span>
+    <span id="fbMiniErrors" class="fbErrors"><!-- 2 errors --></span>
+  </span>
+</span>
+<!-- 
+<div id="fbErrorPopup">
+  <div id="fbErrorPopupContent">
+    <div id="fbErrorIndicator" class="fbErrors">2 errors</div>
+  </div>
+</div>
+ -->
+</body>
+</html>
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.png
new file mode 100644
index 0000000..e10affe
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/firebug.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/group.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/group.gif
new file mode 100644
index 0000000..8db97c2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/group.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/html.css b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/html.css
new file mode 100644
index 0000000..5b7c5f4
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/html.css
@@ -0,0 +1,272 @@
+/* See license.txt for terms of usage */
+
+.panelNode-html {
+    -moz-box-sizing: padding-box;
+    padding: 4px 0 0 2px;
+}
+
+.nodeBox {
+    position: relative;
+    font-family: Monaco, monospace;
+    padding-left: 13px;
+    -moz-user-select: -moz-none;
+}
+.nodeBox.search-selection {
+    -moz-user-select: text;
+}
+.twisty {
+    position: absolute;
+    left: 0px;
+    top: 0px;
+    width: 14px;
+    height: 14px;
+}
+
+.nodeChildBox {
+    margin-left: 12px;
+    display: none;
+}
+
+.nodeLabel,
+.nodeCloseLabel {
+    margin: -2px 2px 0 2px;
+    border: 2px solid transparent;
+    -moz-border-radius: 3px;
+    padding: 0 2px;
+    color: #000088;
+}
+
+.nodeCloseLabel {
+    display: none;
+}
+
+.nodeTag {
+    cursor: pointer;
+    color: blue;
+}
+
+.nodeValue {
+    color: #FF0000;
+    font-weight: normal;
+}
+
+.nodeText,
+.nodeComment {
+    margin: 0 2px;
+    vertical-align: top;
+}
+
+.nodeText {
+    color: #333333;
+}
+
+.nodeWhiteSpace {
+    border: 1px solid LightGray;
+    white-space: pre; /* otherwise the border will be collapsed around zero pixels */
+    margin-left: 1px;
+    color: gray;
+}
+
+
+.nodeWhiteSpace_Space {
+    border: 1px solid #ddd;
+}
+
+.nodeTextEntity {
+    border: 1px solid gray;
+    white-space: pre; /* otherwise the border will be collapsed around zero pixels */
+    margin-left: 1px;
+}
+
+.nodeComment {
+    color: DarkGreen;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeBox.highlightOpen > .nodeLabel {
+    background-color: #EEEEEE;
+}
+
+.nodeBox.highlightOpen > .nodeCloseLabel,
+.nodeBox.highlightOpen > .nodeChildBox,
+.nodeBox.open > .nodeCloseLabel,
+.nodeBox.open > .nodeChildBox {
+    display: block;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeBox.selected > .nodeLabel > .nodeLabelBox,
+.nodeBox.selected > .nodeLabel {
+    border-color: Highlight;
+    background-color: Highlight;
+    color: HighlightText !important;
+}
+
+.nodeBox.selected > .nodeLabel > .nodeLabelBox,
+.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeTag,
+.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue,
+.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText {
+    color: inherit !important;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeBox.highlighted > .nodeLabel {
+    border-color: Highlight !important;
+    background-color: cyan !important;
+    color: #000000 !important;
+}
+
+.nodeBox.highlighted > .nodeLabel > .nodeLabelBox,
+.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeTag,
+.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue,
+.nodeBox.highlighted > .nodeLabel > .nodeLabelBox > .nodeText {
+    color: #000000 !important;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox,
+.nodeBox.nodeHidden .nodeCloseLabel,
+.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeText,
+.nodeBox.nodeHidden .nodeText {
+    color: #888888;
+}
+
+.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeTag,
+.nodeBox.nodeHidden .nodeCloseLabel > .nodeCloseLabelBox > .nodeTag {
+    color: #5F82D9;
+}
+
+.nodeBox.nodeHidden .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue {
+    color: #D86060;
+}
+
+.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox,
+.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeTag,
+.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue,
+.nodeBox.nodeHidden.selected > .nodeLabel > .nodeLabelBox > .nodeText {
+    color: SkyBlue !important;
+}
+
+/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
+
+.nodeBox.mutated > .nodeLabel,
+.nodeAttr.mutated,
+.nodeValue.mutated,
+.nodeText.mutated,
+.nodeBox.mutated > .nodeText {
+    background-color: #EFFF79;
+    color: #FF0000 !important;
+}
+
+.nodeBox.selected.mutated > .nodeLabel,
+.nodeBox.selected.mutated > .nodeLabel > .nodeLabelBox,
+.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr.mutated > .nodeValue,
+.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeAttr > .nodeValue.mutated,
+.nodeBox.selected > .nodeLabel > .nodeLabelBox > .nodeText.mutated {
+    background-color: #EFFF79;
+    border-color: #EFFF79;
+    color: #FF0000 !important;
+}
+
+/************************************************************************************************/
+
+.logRow-dirxml {
+    padding-left: 0;
+}
+
+.soloElement > .nodeBox  {
+    padding-left: 0;
+}
+
+.useA11y .nodeLabel.focused {
+    outline: 2px solid #FF9933;
+    -moz-outline-radius: 3px;
+    outline-offset: -2px;
+}
+
+.useA11y .nodeLabelBox:focus {
+    outline: none;
+}
+
+/************************************************************************************************/
+
+.breakpointCode .twisty {
+    display: none;
+}
+
+.breakpointCode .nodeBox.containerNodeBox,
+.breakpointCode .nodeLabel {
+    padding-left: 0px;
+    margin-left: 0px;
+    font-family: Monaco, monospace !important;
+}
+
+.breakpointCode .nodeTag,
+.breakpointCode .nodeAttr,
+.breakpointCode .nodeText,
+.breakpointCode .nodeValue,
+.breakpointCode .nodeLabel {
+    color: DarkGreen !important;
+}
+
+.breakpointMutationType {
+    position: absolute;
+    top: 4px;
+    right: 20px;
+    color: gray;
+}
+
+
+
+
+
+
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+/************************************************************************************************/
+
+
+
+/************************************************************************************************/
+/* Twisties */
+
+.twisty,
+.logRow-errorMessage > .hasTwisty > .errorTitle,
+.logRow-log > .objectBox-array.hasTwisty,
+.logRow-spy .spyHead .spyTitle,
+.logGroup > .logRow,
+.memberRow.hasChildren > .memberLabelCell > .memberLabel,
+.hasHeaders .netHrefLabel,
+.netPageRow > .netCol > .netPageTitle {
+    background-image: url(twistyClosed.png);
+    background-repeat: no-repeat;
+    background-position: 2px 2px;
+	min-height: 12px;
+}
+
+.logRow-errorMessage > .hasTwisty.opened > .errorTitle,
+.logRow-log > .objectBox-array.hasTwisty.opened,
+.logRow-spy.opened .spyHead .spyTitle,
+.logGroup.opened > .logRow,
+.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel,
+.nodeBox.highlightOpen > .nodeLabel > .twisty,
+.nodeBox.open > .nodeLabel > .twisty,
+.netRow.opened > .netCol > .netHrefLabel,
+.netPageRow.opened > .netCol > .netPageTitle {
+    background-image: url(twistyOpen.png);
+}
+
+.twisty {
+    background-position: 4px 4px;
+}
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif
new file mode 100644
index 0000000..0618e20
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.png
new file mode 100644
index 0000000..da1e533
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/infoIcon.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/loading_16.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/loading_16.gif
new file mode 100644
index 0000000..085ccae
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/loading_16.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/min.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/min.png
new file mode 100644
index 0000000..1034d66
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/min.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/minHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/minHover.png
new file mode 100644
index 0000000..b0d1e1a
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/minHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/off.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/off.png
new file mode 100644
index 0000000..b70b1d2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/off.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/offHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/offHover.png
new file mode 100644
index 0000000..f3670f1
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/offHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif
new file mode 100644
index 0000000..6865c96
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/pixel_transparent.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg
new file mode 100644
index 0000000..2dfa728
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/roundCorner.svg
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<svg xmlns="http://www.w3.org/2000/svg">
+  <rect fill="white"  x="0" y="0" width="100%" height="100%" />
+  <rect fill="highlight"  x="0" y="0" width="100%" height="100%" rx="2px"/>
+</svg>
+
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/search.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/search.gif
new file mode 100644
index 0000000..2a62098
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/search.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/search.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/search.png
new file mode 100644
index 0000000..fba33b8
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/search.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadow.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadow.gif
new file mode 100644
index 0000000..af7f537
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadow.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadow2.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadow2.gif
new file mode 100644
index 0000000..099cbf3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadow2.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png
new file mode 100644
index 0000000..a2561df
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/shadowAlpha.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/sprite.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/sprite.png
new file mode 100644
index 0000000..33d2c4d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/sprite.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png
new file mode 100644
index 0000000..0fb24d0
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverLeft.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png
new file mode 100644
index 0000000..fbccab5
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverMid.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png
new file mode 100644
index 0000000..3db0f36
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabHoverRight.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabLeft.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabLeft.png
new file mode 100644
index 0000000..a6cc9e9
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabLeft.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png
new file mode 100644
index 0000000..4726e62
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuCheckbox.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png
new file mode 100644
index 0000000..eb4b11e
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuPin.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png
new file mode 100644
index 0000000..55b982d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuRadio.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png
new file mode 100644
index 0000000..957bd9f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTarget.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png
new file mode 100644
index 0000000..200a370
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMenuTargetHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMid.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMid.png
new file mode 100644
index 0000000..68986c3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabMid.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabRight.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabRight.png
new file mode 100644
index 0000000..5011307
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tabRight.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif
new file mode 100644
index 0000000..0ee5497
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png
new file mode 100644
index 0000000..21682c3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorBorders.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif
new file mode 100644
index 0000000..04f8421
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png
new file mode 100644
index 0000000..a0f839d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/textEditorCorners.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png
new file mode 100644
index 0000000..10998ae
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/titlebarMid.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png
new file mode 100644
index 0000000..aa21dee
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/toolbarMid.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tree_close.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tree_close.gif
new file mode 100644
index 0000000..e26728a
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tree_close.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tree_open.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tree_open.gif
new file mode 100644
index 0000000..edf662f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/tree_open.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png
new file mode 100644
index 0000000..f80319b
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/twistyClosed.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png
new file mode 100644
index 0000000..8680124
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/twistyOpen.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/up.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/up.png
new file mode 100644
index 0000000..2174d03
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/up.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/upActive.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/upActive.png
new file mode 100644
index 0000000..236cf67
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/upActive.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/upHover.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/upHover.png
new file mode 100644
index 0000000..cd81317
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/upHover.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif
new file mode 100644
index 0000000..8497278
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.gif
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.png b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.png
new file mode 100644
index 0000000..de51084
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/skin/xp/warningIcon.png
Binary files differ
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/src/firebug-lite-debug.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/src/firebug-lite-debug.js
new file mode 100644
index 0000000..40b1ae7
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/firebug-lite/src/firebug-lite-debug.js
@@ -0,0 +1,31176 @@
+(function(){
+
+/*!*************************************************************
+ *
+ *    Firebug Lite 1.4.0
+ *
+ *      Copyright (c) 2007, Parakey Inc.
+ *      Released under BSD license.
+ *      More information: http://getfirebug.com/firebuglite
+ *
+ **************************************************************/
+
+/*!
+ * CSS selectors powered by:
+ *
+ * Sizzle CSS Selector Engine - v1.0
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+
+/** @namespace describe lib */
+
+// FIXME: xxxpedro if we use "var FBL = {}" the FBL won't appear in the DOM Panel in IE
+var FBL = {};
+
+( /** @scope s_lib @this FBL */ function() {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Constants
+
+var productionDir = "http://getfirebug.com/releases/lite/";
+var bookmarkletVersion = 4;
+
+// ************************************************************************************************
+
+var reNotWhitespace = /[^\s]/;
+var reSplitFile = /:\/{1,3}(.*?)\/([^\/]*?)\/?($|\?.*)/;
+
+// Globals
+this.reJavascript = /\s*javascript:\s*(.*)/;
+this.reChrome = /chrome:\/\/([^\/]*)\//;
+this.reFile = /file:\/\/([^\/]*)\//;
+
+
+// ************************************************************************************************
+// properties
+
+var userAgent = navigator.userAgent.toLowerCase();
+this.isFirefox = /firefox/.test(userAgent);
+this.isOpera   = /opera/.test(userAgent);
+this.isSafari  = /webkit/.test(userAgent);
+this.isIE      = /msie/.test(userAgent) && !/opera/.test(userAgent);
+this.isIE6     = /msie 6/i.test(navigator.appVersion);
+this.browserVersion = (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1];
+this.isIElt8   = this.isIE && (this.browserVersion-0 < 8);
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.NS = null;
+this.pixelsPerInch = null;
+
+
+// ************************************************************************************************
+// Namespaces
+
+var namespaces = [];
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.ns = function(fn)
+{
+    var ns = {};
+    namespaces.push(fn, ns);
+    return ns;
+};
+
+var FBTrace = null;
+
+this.initialize = function()
+{
+    // Firebug Lite is already running in persistent mode so we just quit
+    if (window.firebug && firebug.firebuglite || window.console && console.firebuglite)
+        return;
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // initialize environment
+
+    // point the FBTrace object to the local variable
+    if (FBL.FBTrace)
+        FBTrace = FBL.FBTrace;
+    else
+        FBTrace = FBL.FBTrace = {};
+
+    // check if the actual window is a persisted chrome context
+    var isChromeContext = window.Firebug && typeof window.Firebug.SharedEnv == "object";
+
+    // chrome context of the persistent application
+    if (isChromeContext)
+    {
+        // TODO: xxxpedro persist - make a better synchronization
+        sharedEnv = window.Firebug.SharedEnv;
+        delete window.Firebug.SharedEnv;
+
+        FBL.Env = sharedEnv;
+        FBL.Env.isChromeContext = true;
+        FBTrace.messageQueue = FBL.Env.traceMessageQueue;
+    }
+    // non-persistent application
+    else
+    {
+        FBL.NS = document.documentElement.namespaceURI;
+        FBL.Env.browser = window;
+        FBL.Env.destroy = destroyEnvironment;
+
+        if (document.documentElement.getAttribute("debug") == "true")
+            FBL.Env.Options.startOpened = true;
+
+        // find the URL location of the loaded application
+        findLocation();
+
+        // TODO: get preferences here...
+        // The problem is that we don't have the Firebug object yet, so we can't use
+        // Firebug.loadPrefs. We're using the Store module directly instead.
+        var prefs = FBL.Store.get("FirebugLite") || {};
+        FBL.Env.DefaultOptions = FBL.Env.Options;
+        FBL.Env.Options = FBL.extend(FBL.Env.Options, prefs.options || {});
+
+        if (FBL.isFirefox &&
+            typeof FBL.Env.browser.console == "object" &&
+            FBL.Env.browser.console.firebug &&
+            FBL.Env.Options.disableWhenFirebugActive)
+                return;
+    }
+
+    // exposes the FBL to the global namespace when in debug mode
+    if (FBL.Env.isDebugMode)
+    {
+        FBL.Env.browser.FBL = FBL;
+    }
+
+    // check browser compatibilities
+    this.isQuiksMode = FBL.Env.browser.document.compatMode == "BackCompat";
+    this.isIEQuiksMode = this.isIE && this.isQuiksMode;
+    this.isIEStantandMode = this.isIE && !this.isQuiksMode;
+
+    this.noFixedPosition = this.isIE6 || this.isIEQuiksMode;
+
+    // after creating/synchronizing the environment, initialize the FBTrace module
+    if (FBL.Env.Options.enableTrace) FBTrace.initialize();
+
+    if (FBTrace.DBG_INITIALIZE && isChromeContext) FBTrace.sysout("FBL.initialize - persistent application", "initialize chrome context");
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // initialize namespaces
+
+    if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FBL.initialize", namespaces.length/2+" namespaces BEGIN");
+
+    for (var i = 0; i < namespaces.length; i += 2)
+    {
+        var fn = namespaces[i];
+        var ns = namespaces[i+1];
+        fn.apply(ns);
+    }
+
+    if (FBTrace.DBG_INITIALIZE) {
+        FBTrace.sysout("FBL.initialize", namespaces.length/2+" namespaces END");
+        FBTrace.sysout("FBL waitForDocument", "waiting document load");
+    }
+
+    FBL.Ajax.initialize();
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // finish environment initialization
+    FBL.Firebug.loadPrefs();
+
+    if (FBL.Env.Options.enablePersistent)
+    {
+        // TODO: xxxpedro persist - make a better synchronization
+        if (isChromeContext)
+        {
+            FBL.FirebugChrome.clone(FBL.Env.FirebugChrome);
+        }
+        else
+        {
+            FBL.Env.FirebugChrome = FBL.FirebugChrome;
+            FBL.Env.traceMessageQueue = FBTrace.messageQueue;
+        }
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // wait document load
+
+    waitForDocument();
+};
+
+var waitForDocument = function waitForDocument()
+{
+    // document.body not available in XML+XSL documents in Firefox
+    var doc = FBL.Env.browser.document;
+    var body = doc.getElementsByTagName("body")[0];
+
+    if (body)
+    {
+        calculatePixelsPerInch(doc, body);
+        onDocumentLoad();
+    }
+    else
+        setTimeout(waitForDocument, 50);
+};
+
+var onDocumentLoad = function onDocumentLoad()
+{
+    if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FBL onDocumentLoad", "document loaded");
+
+    // fix IE6 problem with cache of background images, causing a lot of flickering
+    if (FBL.isIE6)
+        fixIE6BackgroundImageCache();
+
+    // chrome context of the persistent application
+    if (FBL.Env.Options.enablePersistent && FBL.Env.isChromeContext)
+    {
+        // finally, start the application in the chrome context
+        FBL.Firebug.initialize();
+
+        // if is not development mode, remove the shared environment cache object
+        // used to synchronize the both persistent contexts
+        if (!FBL.Env.isDevelopmentMode)
+        {
+            sharedEnv.destroy();
+            sharedEnv = null;
+        }
+    }
+    // non-persistent application
+    else
+    {
+        FBL.FirebugChrome.create();
+    }
+};
+
+// ************************************************************************************************
+// Env
+
+var sharedEnv;
+
+this.Env =
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Env Options (will be transported to Firebug options)
+    Options:
+    {
+        saveCookies: true,
+
+        saveWindowPosition: false,
+        saveCommandLineHistory: false,
+
+        startOpened: false,
+        startInNewWindow: false,
+        showIconWhenHidden: true,
+
+        overrideConsole: true,
+        ignoreFirebugElements: true,
+        disableWhenFirebugActive: true,
+
+        disableXHRListener: false,
+        disableResourceFetching: false,
+
+        enableTrace: false,
+        enablePersistent: false
+
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Library location
+    Location:
+    {
+        sourceDir: null,
+        baseDir: null,
+        skinDir: null,
+        skin: null,
+        app: null
+    },
+
+    skin: "xp",
+    useLocalSkin: false,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Env states
+    isDevelopmentMode: false,
+    isDebugMode: false,
+    isChromeContext: false,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Env references
+    browser: null,
+    chrome: null
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var destroyEnvironment = function destroyEnvironment()
+{
+    setTimeout(function()
+    {
+        FBL = null;
+    }, 100);
+};
+
+// ************************************************************************************************
+// Library location
+
+var findLocation =  function findLocation()
+{
+    var reFirebugFile = /(firebug-lite(?:-\w+)?(?:\.js|\.jgz))(?:#(.+))?$/;
+    var reGetFirebugSite = /(?:http|https):\/\/getfirebug.com\//;
+    var isGetFirebugSite;
+
+    var rePath = /^(.*\/)/;
+    var reProtocol = /^\w+:\/\//;
+    var path = null;
+    var doc = document;
+
+    // Firebug Lite 1.3.0 bookmarklet identification
+    var script = doc.getElementById("FirebugLite");
+
+    var scriptSrc;
+    var hasSrcAttribute = true;
+
+    // If the script was loaded via bookmarklet, we already have the script tag
+    if (script)
+    {
+        scriptSrc = script.src;
+        file = reFirebugFile.exec(scriptSrc);
+
+        var version = script.getAttribute("FirebugLite");
+        var number = version ? parseInt(version) : 0;
+
+        if (!version || !number || number < bookmarkletVersion)
+        {
+            FBL.Env.bookmarkletOutdated = true;
+        }
+    }
+    // otherwise we must search for the correct script tag
+    else
+    {
+        for(var i=0, s=doc.getElementsByTagName("script"), si; si=s[i]; i++)
+        {
+            var file = null;
+            if ( si.nodeName.toLowerCase() == "script" )
+            {
+                if (file = reFirebugFile.exec(si.getAttribute("firebugSrc")))
+                {
+                    scriptSrc = si.getAttribute("firebugSrc");
+                    hasSrcAttribute = false;
+                }
+                else if (file = reFirebugFile.exec(si.src))
+                {
+                    scriptSrc = si.src;
+                }
+                else
+                    continue;
+
+                script = si;
+                break;
+            }
+        }
+    }
+
+    // mark the script tag to be ignored by Firebug Lite
+    if (script)
+        script.firebugIgnore = true;
+
+    if (file)
+    {
+        var fileName = file[1];
+        var fileOptions = file[2];
+
+        // absolute path
+        if (reProtocol.test(scriptSrc)) {
+            path = rePath.exec(scriptSrc)[1];
+
+        }
+        // relative path
+        else
+        {
+            var r = rePath.exec(scriptSrc);
+            var src = r ? r[1] : scriptSrc;
+            var backDir = /^((?:\.\.\/)+)(.*)/.exec(src);
+            var reLastDir = /^(.*\/)[^\/]+\/$/;
+            path = rePath.exec(location.href)[1];
+
+            // "../some/path"
+            if (backDir)
+            {
+                var j = backDir[1].length/3;
+                var p;
+                while (j-- > 0)
+                    path = reLastDir.exec(path)[1];
+
+                path += backDir[2];
+            }
+
+            else if(src.indexOf("/") != -1)
+            {
+                // "./some/path"
+                if(/^\.\/./.test(src))
+                {
+                    path += src.substring(2);
+                }
+                // "/some/path"
+                else if(/^\/./.test(src))
+                {
+                    var domain = /^(\w+:\/\/[^\/]+)/.exec(path);
+                    path = domain[1] + src;
+                }
+                // "some/path"
+                else
+                {
+                    path += src;
+                }
+            }
+        }
+    }
+
+    FBL.Env.isChromeExtension = script && script.getAttribute("extension") == "Chrome";
+    if (FBL.Env.isChromeExtension)
+    {
+        path = productionDir;
+        FBL.Env.bookmarkletOutdated = false;
+        script = {innerHTML: "{showIconWhenHidden:false}"};
+    }
+
+    isGetFirebugSite = reGetFirebugSite.test(path);
+
+    if (isGetFirebugSite && path.indexOf("/releases/lite/") == -1)
+    {
+        // See Issue 4587 - If we are loading the script from getfirebug.com shortcut, like
+        // https://getfirebug.com/firebug-lite.js, then we must manually add the full path,
+        // otherwise the Env.Location will hold the wrong path, which will in turn lead to
+        // undesirable effects like the problem in Issue 4587
+        path += "releases/lite/" + (fileName == "firebug-lite-beta.js" ? "beta/" : "latest/");
+    }
+
+    var m = path && path.match(/([^\/]+)\/$/) || null;
+
+    if (path && m)
+    {
+        var Env = FBL.Env;
+
+        // Always use the local skin when running in the same domain
+        // See Issue 3554: Firebug Lite should use local images when loaded locally
+        Env.useLocalSkin = path.indexOf(location.protocol + "//" + location.host + "/") == 0 &&
+                // but we cannot use the locan skin when loaded from getfirebug.com, otherwise
+                // the bookmarklet won't work when visiting getfirebug.com
+                !isGetFirebugSite;
+
+        // detecting development and debug modes via file name
+        if (fileName == "firebug-lite-dev.js")
+        {
+            Env.isDevelopmentMode = true;
+            Env.isDebugMode = true;
+        }
+        else if (fileName == "firebug-lite-debug.js")
+        {
+            Env.isDebugMode = true;
+        }
+
+        // process the <html debug="true">
+        if (Env.browser.document.documentElement.getAttribute("debug") == "true")
+        {
+            Env.Options.startOpened = true;
+        }
+
+        // process the Script URL Options
+        if (fileOptions)
+        {
+            var options = fileOptions.split(",");
+
+            for (var i = 0, length = options.length; i < length; i++)
+            {
+                var option = options[i];
+                var name, value;
+
+                if (option.indexOf("=") != -1)
+                {
+                    var parts = option.split("=");
+                    name = parts[0];
+                    value = eval(unescape(parts[1]));
+                }
+                else
+                {
+                    name = option;
+                    value = true;
+                }
+
+                if (name == "debug")
+                {
+                    Env.isDebugMode = !!value;
+                }
+                else if (name in Env.Options)
+                {
+                    Env.Options[name] = value;
+                }
+                else
+                {
+                    Env[name] = value;
+                }
+            }
+        }
+
+        // process the Script JSON Options
+        if (hasSrcAttribute)
+        {
+            var innerOptions = FBL.trim(script.innerHTML);
+            if (innerOptions)
+            {
+                var innerOptionsObject = eval("(" + innerOptions + ")");
+
+                for (var name in innerOptionsObject)
+                {
+                    var value = innerOptionsObject[name];
+
+                    if (name == "debug")
+                    {
+                        Env.isDebugMode = !!value;
+                    }
+                    else if (name in Env.Options)
+                    {
+                        Env.Options[name] = value;
+                    }
+                    else
+                    {
+                        Env[name] = value;
+                    }
+                }
+            }
+        }
+
+        if (!Env.Options.saveCookies)
+            FBL.Store.remove("FirebugLite");
+
+        // process the Debug Mode
+        if (Env.isDebugMode)
+        {
+            Env.Options.startOpened = true;
+            Env.Options.enableTrace = true;
+            Env.Options.disableWhenFirebugActive = false;
+        }
+
+        var loc = Env.Location;
+        var isProductionRelease = path.indexOf(productionDir) != -1;
+
+        loc.sourceDir = path;
+        loc.baseDir = path.substr(0, path.length - m[1].length - 1);
+        loc.skinDir = (isProductionRelease ? path : loc.baseDir) + "skin/" + Env.skin + "/";
+        loc.skin = loc.skinDir + "firebug.html";
+        loc.app = path + fileName;
+    }
+    else
+    {
+        throw new Error("Firebug Error: Library path not found");
+    }
+};
+
+// ************************************************************************************************
+// Basics
+
+this.bind = function()  // fn, thisObject, args => thisObject.fn(args, arguments);
+{
+   var args = cloneArray(arguments), fn = args.shift(), object = args.shift();
+   return function() { return fn.apply(object, arrayInsert(cloneArray(args), 0, arguments)); };
+};
+
+this.bindFixed = function() // fn, thisObject, args => thisObject.fn(args);
+{
+    var args = cloneArray(arguments), fn = args.shift(), object = args.shift();
+    return function() { return fn.apply(object, args); };
+};
+
+this.extend = function(l, r)
+{
+    var newOb = {};
+    for (var n in l)
+        newOb[n] = l[n];
+    for (var n in r)
+        newOb[n] = r[n];
+    return newOb;
+};
+
+this.descend = function(prototypeParent, childProperties)
+{
+    function protoSetter() {};
+    protoSetter.prototype = prototypeParent;
+    var newOb = new protoSetter();
+    for (var n in childProperties)
+        newOb[n] = childProperties[n];
+    return newOb;
+};
+
+this.append = function(l, r)
+{
+    for (var n in r)
+        l[n] = r[n];
+
+    return l;
+};
+
+this.keys = function(map)  // At least sometimes the keys will be on user-level window objects
+{
+    var keys = [];
+    try
+    {
+        for (var name in map)  // enumeration is safe
+            keys.push(name);   // name is string, safe
+    }
+    catch (exc)
+    {
+        // Sometimes we get exceptions trying to iterate properties
+    }
+
+    return keys;  // return is safe
+};
+
+this.values = function(map)
+{
+    var values = [];
+    try
+    {
+        for (var name in map)
+        {
+            try
+            {
+                values.push(map[name]);
+            }
+            catch (exc)
+            {
+                // Sometimes we get exceptions trying to access properties
+                if (FBTrace.DBG_ERRORS)
+                    FBTrace.sysout("lib.values FAILED ", exc);
+            }
+
+        }
+    }
+    catch (exc)
+    {
+        // Sometimes we get exceptions trying to iterate properties
+        if (FBTrace.DBG_ERRORS)
+            FBTrace.sysout("lib.values FAILED ", exc);
+    }
+
+    return values;
+};
+
+this.remove = function(list, item)
+{
+    for (var i = 0; i < list.length; ++i)
+    {
+        if (list[i] == item)
+        {
+            list.splice(i, 1);
+            break;
+        }
+    }
+};
+
+this.sliceArray = function(array, index)
+{
+    var slice = [];
+    for (var i = index; i < array.length; ++i)
+        slice.push(array[i]);
+
+    return slice;
+};
+
+function cloneArray(array, fn)
+{
+   var newArray = [];
+
+   if (fn)
+       for (var i = 0; i < array.length; ++i)
+           newArray.push(fn(array[i]));
+   else
+       for (var i = 0; i < array.length; ++i)
+           newArray.push(array[i]);
+
+   return newArray;
+}
+
+function extendArray(array, array2)
+{
+   var newArray = [];
+   newArray.push.apply(newArray, array);
+   newArray.push.apply(newArray, array2);
+   return newArray;
+}
+
+this.extendArray = extendArray;
+this.cloneArray = cloneArray;
+
+function arrayInsert(array, index, other)
+{
+   for (var i = 0; i < other.length; ++i)
+       array.splice(i+index, 0, other[i]);
+
+   return array;
+}
+
+// ************************************************************************************************
+
+this.createStyleSheet = function(doc, url)
+{
+    //TODO: xxxpedro
+    //var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
+    var style = this.createElement("link");
+    style.setAttribute("charset","utf-8");
+    style.firebugIgnore = true;
+    style.setAttribute("rel", "stylesheet");
+    style.setAttribute("type", "text/css");
+    style.setAttribute("href", url);
+
+    //TODO: xxxpedro
+    //style.innerHTML = this.getResource(url);
+    return style;
+};
+
+this.addStyleSheet = function(doc, style)
+{
+    var heads = doc.getElementsByTagName("head");
+    if (heads.length)
+        heads[0].appendChild(style);
+    else
+        doc.documentElement.appendChild(style);
+};
+
+this.appendStylesheet = function(doc, uri)
+{
+    // Make sure the stylesheet is not appended twice.
+    if (this.$(uri, doc))
+        return;
+
+    var styleSheet = this.createStyleSheet(doc, uri);
+    styleSheet.setAttribute("id", uri);
+    this.addStyleSheet(doc, styleSheet);
+};
+
+this.addScript = function(doc, id, src)
+{
+    var element = doc.createElementNS("http://www.w3.org/1999/xhtml", "html:script");
+    element.setAttribute("type", "text/javascript");
+    element.setAttribute("id", id);
+    if (!FBTrace.DBG_CONSOLE)
+        FBL.unwrapObject(element).firebugIgnore = true;
+
+    element.innerHTML = src;
+    if (doc.documentElement)
+        doc.documentElement.appendChild(element);
+    else
+    {
+        // See issue 1079, the svg test case gives this error
+        if (FBTrace.DBG_ERRORS)
+            FBTrace.sysout("lib.addScript doc has no documentElement:", doc);
+    }
+    return element;
+};
+
+
+// ************************************************************************************************
+
+this.getStyle = this.isIE ?
+    function(el, name)
+    {
+        return el.currentStyle[name] || el.style[name] || undefined;
+    }
+    :
+    function(el, name)
+    {
+        return el.ownerDocument.defaultView.getComputedStyle(el,null)[name]
+            || el.style[name] || undefined;
+    };
+
+
+// ************************************************************************************************
+// Whitespace and Entity conversions
+
+var entityConversionLists = this.entityConversionLists = {
+    normal : {
+        whitespace : {
+            '\t' : '\u200c\u2192',
+            '\n' : '\u200c\u00b6',
+            '\r' : '\u200c\u00ac',
+            ' '  : '\u200c\u00b7'
+        }
+    },
+    reverse : {
+        whitespace : {
+            '&Tab;' : '\t',
+            '&NewLine;' : '\n',
+            '\u200c\u2192' : '\t',
+            '\u200c\u00b6' : '\n',
+            '\u200c\u00ac' : '\r',
+            '\u200c\u00b7' : ' '
+        }
+    }
+};
+
+var normal = entityConversionLists.normal,
+    reverse = entityConversionLists.reverse;
+
+function addEntityMapToList(ccode, entity)
+{
+    var lists = Array.prototype.slice.call(arguments, 2),
+        len = lists.length,
+        ch = String.fromCharCode(ccode);
+    for (var i = 0; i < len; i++)
+    {
+        var list = lists[i];
+        normal[list]=normal[list] || {};
+        normal[list][ch] = '&' + entity + ';';
+        reverse[list]=reverse[list] || {};
+        reverse[list]['&' + entity + ';'] = ch;
+    }
+};
+
+var e = addEntityMapToList,
+    white = 'whitespace',
+    text = 'text',
+    attr = 'attributes',
+    css = 'css',
+    editor = 'editor';
+
+e(0x0022, 'quot', attr, css);
+e(0x0026, 'amp', attr, text, css);
+e(0x0027, 'apos', css);
+e(0x003c, 'lt', attr, text, css);
+e(0x003e, 'gt', attr, text, css);
+e(0xa9, 'copy', text, editor);
+e(0xae, 'reg', text, editor);
+e(0x2122, 'trade', text, editor);
+
+// See http://en.wikipedia.org/wiki/Dash
+e(0x2012, '#8210', attr, text, editor); // figure dash
+e(0x2013, 'ndash', attr, text, editor); // en dash
+e(0x2014, 'mdash', attr, text, editor); // em dash
+e(0x2015, '#8213', attr, text, editor); // horizontal bar
+
+e(0x00a0, 'nbsp', attr, text, white, editor);
+e(0x2002, 'ensp', attr, text, white, editor);
+e(0x2003, 'emsp', attr, text, white, editor);
+e(0x2009, 'thinsp', attr, text, white, editor);
+e(0x200c, 'zwnj', attr, text, white, editor);
+e(0x200d, 'zwj', attr, text, white, editor);
+e(0x200e, 'lrm', attr, text, white, editor);
+e(0x200f, 'rlm', attr, text, white, editor);
+e(0x200b, '#8203', attr, text, white, editor); // zero-width space (ZWSP)
+
+//************************************************************************************************
+// Entity escaping
+
+var entityConversionRegexes = {
+        normal : {},
+        reverse : {}
+    };
+
+var escapeEntitiesRegEx = {
+    normal : function(list)
+    {
+        var chars = [];
+        for ( var ch in list)
+        {
+            chars.push(ch);
+        }
+        return new RegExp('([' + chars.join('') + '])', 'gm');
+    },
+    reverse : function(list)
+    {
+        var chars = [];
+        for ( var ch in list)
+        {
+            chars.push(ch);
+        }
+        return new RegExp('(' + chars.join('|') + ')', 'gm');
+    }
+};
+
+function getEscapeRegexp(direction, lists)
+{
+    var name = '', re;
+    var groups = [].concat(lists);
+    for (i = 0; i < groups.length; i++)
+    {
+        name += groups[i].group;
+    }
+    re = entityConversionRegexes[direction][name];
+    if (!re)
+    {
+        var list = {};
+        if (groups.length > 1)
+        {
+            for ( var i = 0; i < groups.length; i++)
+            {
+                var aList = entityConversionLists[direction][groups[i].group];
+                for ( var item in aList)
+                    list[item] = aList[item];
+            }
+        } else if (groups.length==1)
+        {
+            list = entityConversionLists[direction][groups[0].group]; // faster for special case
+        } else {
+            list = {}; // perhaps should print out an error here?
+        }
+        re = entityConversionRegexes[direction][name] = escapeEntitiesRegEx[direction](list);
+    }
+    return re;
+};
+
+function createSimpleEscape(name, direction)
+{
+    return function(value)
+    {
+        var list = entityConversionLists[direction][name];
+        return String(value).replace(
+                getEscapeRegexp(direction, {
+                    group : name,
+                    list : list
+                }),
+                function(ch)
+                {
+                    return list[ch];
+                }
+               );
+    };
+};
+
+function escapeGroupsForEntities(str, lists)
+{
+    lists = [].concat(lists);
+    var re = getEscapeRegexp('normal', lists),
+        split = String(str).split(re),
+        len = split.length,
+        results = [],
+        cur, r, i, ri = 0, l, list, last = '';
+    if (!len)
+        return [ {
+            str : String(str),
+            group : '',
+            name : ''
+        } ];
+    for (i = 0; i < len; i++)
+    {
+        cur = split[i];
+        if (cur == '')
+            continue;
+        for (l = 0; l < lists.length; l++)
+        {
+            list = lists[l];
+            r = entityConversionLists.normal[list.group][cur];
+            // if (cur == ' ' && list.group == 'whitespace' && last == ' ') // only show for runs of more than one space
+            //     r = ' ';
+            if (r)
+            {
+                results[ri] = {
+                    'str' : r,
+                    'class' : list['class'],
+                    'extra' : list.extra[cur] ? list['class']
+                            + list.extra[cur] : ''
+                };
+                break;
+            }
+        }
+        // last=cur;
+        if (!r)
+            results[ri] = {
+                'str' : cur,
+                'class' : '',
+                'extra' : ''
+            };
+        ri++;
+    }
+    return results;
+};
+
+this.escapeGroupsForEntities = escapeGroupsForEntities;
+
+
+function unescapeEntities(str, lists)
+{
+    var re = getEscapeRegexp('reverse', lists),
+        split = String(str).split(re),
+        len = split.length,
+        results = [],
+        cur, r, i, ri = 0, l, list;
+    if (!len)
+        return str;
+    lists = [].concat(lists);
+    for (i = 0; i < len; i++)
+    {
+        cur = split[i];
+        if (cur == '')
+            continue;
+        for (l = 0; l < lists.length; l++)
+        {
+            list = lists[l];
+            r = entityConversionLists.reverse[list.group][cur];
+            if (r)
+            {
+                results[ri] = r;
+                break;
+            }
+        }
+        if (!r)
+            results[ri] = cur;
+        ri++;
+    }
+    return results.join('') || '';
+};
+
+
+// ************************************************************************************************
+// String escaping
+
+var escapeForTextNode = this.escapeForTextNode = createSimpleEscape('text', 'normal');
+var escapeForHtmlEditor = this.escapeForHtmlEditor = createSimpleEscape('editor', 'normal');
+var escapeForElementAttribute = this.escapeForElementAttribute = createSimpleEscape('attributes', 'normal');
+var escapeForCss = this.escapeForCss = createSimpleEscape('css', 'normal');
+
+// deprecated compatibility functions
+//this.deprecateEscapeHTML = createSimpleEscape('text', 'normal');
+//this.deprecatedUnescapeHTML = createSimpleEscape('text', 'reverse');
+//this.escapeHTML = deprecated("use appropriate escapeFor... function", this.deprecateEscapeHTML);
+//this.unescapeHTML = deprecated("use appropriate unescapeFor... function", this.deprecatedUnescapeHTML);
+
+var escapeForSourceLine = this.escapeForSourceLine = createSimpleEscape('text', 'normal');
+
+var unescapeWhitespace = createSimpleEscape('whitespace', 'reverse');
+
+this.unescapeForTextNode = function(str)
+{
+    if (Firebug.showTextNodesWithWhitespace)
+        str = unescapeWhitespace(str);
+    if (!Firebug.showTextNodesWithEntities)
+        str = escapeForElementAttribute(str);
+    return str;
+};
+
+this.escapeNewLines = function(value)
+{
+    return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n");
+};
+
+this.stripNewLines = function(value)
+{
+    return typeof(value) == "string" ? value.replace(/[\r\n]/g, " ") : value;
+};
+
+this.escapeJS = function(value)
+{
+    return value.replace(/\r/g, "\\r").replace(/\n/g, "\\n").replace('"', '\\"', "g");
+};
+
+function escapeHTMLAttribute(value)
+{
+    function replaceChars(ch)
+    {
+        switch (ch)
+        {
+            case "&":
+                return "&amp;";
+            case "'":
+                return apos;
+            case '"':
+                return quot;
+        }
+        return "?";
+    };
+    var apos = "&#39;", quot = "&quot;", around = '"';
+    if( value.indexOf('"') == -1 ) {
+        quot = '"';
+        apos = "'";
+    } else if( value.indexOf("'") == -1 ) {
+        quot = '"';
+        around = "'";
+    }
+    return around + (String(value).replace(/[&'"]/g, replaceChars)) + around;
+}
+
+
+function escapeHTML(value)
+{
+    function replaceChars(ch)
+    {
+        switch (ch)
+        {
+            case "<":
+                return "&lt;";
+            case ">":
+                return "&gt;";
+            case "&":
+                return "&amp;";
+            case "'":
+                return "&#39;";
+            case '"':
+                return "&quot;";
+        }
+        return "?";
+    };
+    return String(value).replace(/[<>&"']/g, replaceChars);
+}
+
+this.escapeHTML = escapeHTML;
+
+this.cropString = function(text, limit)
+{
+    text = text + "";
+
+    if (!limit)
+        var halfLimit = 50;
+    else
+        var halfLimit = limit / 2;
+
+    if (text.length > limit)
+        return this.escapeNewLines(text.substr(0, halfLimit) + "..." + text.substr(text.length-halfLimit));
+    else
+        return this.escapeNewLines(text);
+};
+
+this.isWhitespace = function(text)
+{
+    return !reNotWhitespace.exec(text);
+};
+
+this.splitLines = function(text)
+{
+    var reSplitLines2 = /.*(:?\r\n|\n|\r)?/mg;
+    var lines;
+    if (text.match)
+    {
+        lines = text.match(reSplitLines2);
+    }
+    else
+    {
+        var str = text+"";
+        lines = str.match(reSplitLines2);
+    }
+    lines.pop();
+    return lines;
+};
+
+
+// ************************************************************************************************
+
+this.safeToString = function(ob)
+{
+    if (this.isIE)
+    {
+        try
+        {
+            // FIXME: xxxpedro this is failing in IE for the global "external" object
+            return ob + "";
+        }
+        catch(E)
+        {
+            FBTrace.sysout("Lib.safeToString() failed for ", ob);
+            return "";
+        }
+    }
+
+    try
+    {
+        if (ob && "toString" in ob && typeof ob.toString == "function")
+            return ob.toString();
+    }
+    catch (exc)
+    {
+        // xxxpedro it is not safe to use ob+""?
+        return ob + "";
+        ///return "[an object with no toString() function]";
+    }
+};
+
+// ************************************************************************************************
+
+this.hasProperties = function(ob)
+{
+    try
+    {
+        for (var name in ob)
+            return true;
+    } catch (exc) {}
+    return false;
+};
+
+// ************************************************************************************************
+// String Util
+
+var reTrim = /^\s+|\s+$/g;
+this.trim = function(s)
+{
+    return s.replace(reTrim, "");
+};
+
+
+// ************************************************************************************************
+// Empty
+
+this.emptyFn = function(){};
+
+
+
+// ************************************************************************************************
+// Visibility
+
+this.isVisible = function(elt)
+{
+    /*
+    if (elt instanceof XULElement)
+    {
+        //FBTrace.sysout("isVisible elt.offsetWidth: "+elt.offsetWidth+" offsetHeight:"+ elt.offsetHeight+" localName:"+ elt.localName+" nameSpace:"+elt.nameSpaceURI+"\n");
+        return (!elt.hidden && !elt.collapsed);
+    }
+    /**/
+
+    return this.getStyle(elt, "visibility") != "hidden" &&
+        ( elt.offsetWidth > 0 || elt.offsetHeight > 0
+        || elt.tagName in invisibleTags
+        || elt.namespaceURI == "http://www.w3.org/2000/svg"
+        || elt.namespaceURI == "http://www.w3.org/1998/Math/MathML" );
+};
+
+this.collapse = function(elt, collapsed)
+{
+    // IE6 doesn't support the [collapsed] CSS selector. IE7 does support the selector,
+    // but it is causing a bug (the element disappears when you set the "collapsed"
+    // attribute, but it doesn't appear when you remove the attribute. So, for those
+    // cases, we need to use the class attribute.
+    if (this.isIElt8)
+    {
+        if (collapsed)
+            this.setClass(elt, "collapsed");
+        else
+            this.removeClass(elt, "collapsed");
+    }
+    else
+        elt.setAttribute("collapsed", collapsed ? "true" : "false");
+};
+
+this.obscure = function(elt, obscured)
+{
+    if (obscured)
+        this.setClass(elt, "obscured");
+    else
+        this.removeClass(elt, "obscured");
+};
+
+this.hide = function(elt, hidden)
+{
+    elt.style.visibility = hidden ? "hidden" : "visible";
+};
+
+this.clearNode = function(node)
+{
+    var nodeName = " " + node.nodeName.toLowerCase() + " ";
+    var ignoreTags = " table tbody thead tfoot th tr td ";
+
+    // IE can't use innerHTML of table elements
+    if (this.isIE && ignoreTags.indexOf(nodeName) != -1)
+        this.eraseNode(node);
+    else
+        node.innerHTML = "";
+};
+
+this.eraseNode = function(node)
+{
+    while (node.lastChild)
+        node.removeChild(node.lastChild);
+};
+
+// ************************************************************************************************
+// Window iteration
+
+this.iterateWindows = function(win, handler)
+{
+    if (!win || !win.document)
+        return;
+
+    handler(win);
+
+    if (win == top || !win.frames) return; // XXXjjb hack for chromeBug
+
+    for (var i = 0; i < win.frames.length; ++i)
+    {
+        var subWin = win.frames[i];
+        if (subWin != win)
+            this.iterateWindows(subWin, handler);
+    }
+};
+
+this.getRootWindow = function(win)
+{
+    for (; win; win = win.parent)
+    {
+        if (!win.parent || win == win.parent || !this.instanceOf(win.parent, "Window"))
+            return win;
+    }
+    return null;
+};
+
+// ************************************************************************************************
+// Graphics
+
+this.getClientOffset = function(elt)
+{
+    var addOffset = function addOffset(elt, coords, view)
+    {
+        var p = elt.offsetParent;
+
+        ///var style = isIE ? elt.currentStyle : view.getComputedStyle(elt, "");
+        var chrome = Firebug.chrome;
+
+        if (elt.offsetLeft)
+            ///coords.x += elt.offsetLeft + parseInt(style.borderLeftWidth);
+            coords.x += elt.offsetLeft + chrome.getMeasurementInPixels(elt, "borderLeft");
+        if (elt.offsetTop)
+            ///coords.y += elt.offsetTop + parseInt(style.borderTopWidth);
+            coords.y += elt.offsetTop + chrome.getMeasurementInPixels(elt, "borderTop");
+
+        if (p)
+        {
+            if (p.nodeType == 1)
+                addOffset(p, coords, view);
+        }
+        else
+        {
+            var otherView = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView;
+            // IE will fail when reading the frameElement property of a popup window.
+            // We don't need it anyway once it is outside the (popup) viewport, so we're
+            // ignoring the frameElement check when the window is a popup
+            if (!otherView.opener && otherView.frameElement)
+                addOffset(otherView.frameElement, coords, otherView);
+        }
+    };
+
+    var isIE = this.isIE;
+    var coords = {x: 0, y: 0};
+    if (elt)
+    {
+        var view = isIE ? elt.ownerDocument.parentWindow : elt.ownerDocument.defaultView;
+        addOffset(elt, coords, view);
+    }
+
+    return coords;
+};
+
+this.getViewOffset = function(elt, singleFrame)
+{
+    function addOffset(elt, coords, view)
+    {
+        var p = elt.offsetParent;
+        coords.x += elt.offsetLeft - (p ? p.scrollLeft : 0);
+        coords.y += elt.offsetTop - (p ? p.scrollTop : 0);
+
+        if (p)
+        {
+            if (p.nodeType == 1)
+            {
+                var parentStyle = view.getComputedStyle(p, "");
+                if (parentStyle.position != "static")
+                {
+                    coords.x += parseInt(parentStyle.borderLeftWidth);
+                    coords.y += parseInt(parentStyle.borderTopWidth);
+
+                    if (p.localName == "TABLE")
+                    {
+                        coords.x += parseInt(parentStyle.paddingLeft);
+                        coords.y += parseInt(parentStyle.paddingTop);
+                    }
+                    else if (p.localName == "BODY")
+                    {
+                        var style = view.getComputedStyle(elt, "");
+                        coords.x += parseInt(style.marginLeft);
+                        coords.y += parseInt(style.marginTop);
+                    }
+                }
+                else if (p.localName == "BODY")
+                {
+                    coords.x += parseInt(parentStyle.borderLeftWidth);
+                    coords.y += parseInt(parentStyle.borderTopWidth);
+                }
+
+                var parent = elt.parentNode;
+                while (p != parent)
+                {
+                    coords.x -= parent.scrollLeft;
+                    coords.y -= parent.scrollTop;
+                    parent = parent.parentNode;
+                }
+                addOffset(p, coords, view);
+            }
+        }
+        else
+        {
+            if (elt.localName == "BODY")
+            {
+                var style = view.getComputedStyle(elt, "");
+                coords.x += parseInt(style.borderLeftWidth);
+                coords.y += parseInt(style.borderTopWidth);
+
+                var htmlStyle = view.getComputedStyle(elt.parentNode, "");
+                coords.x -= parseInt(htmlStyle.paddingLeft);
+                coords.y -= parseInt(htmlStyle.paddingTop);
+            }
+
+            if (elt.scrollLeft)
+                coords.x += elt.scrollLeft;
+            if (elt.scrollTop)
+                coords.y += elt.scrollTop;
+
+            var win = elt.ownerDocument.defaultView;
+            if (win && (!singleFrame && win.frameElement))
+                addOffset(win.frameElement, coords, win);
+        }
+
+    }
+
+    var coords = {x: 0, y: 0};
+    if (elt)
+        addOffset(elt, coords, elt.ownerDocument.defaultView);
+
+    return coords;
+};
+
+this.getLTRBWH = function(elt)
+{
+    var bcrect,
+        dims = {"left": 0, "top": 0, "right": 0, "bottom": 0, "width": 0, "height": 0};
+
+    if (elt)
+    {
+        bcrect = elt.getBoundingClientRect();
+        dims.left = bcrect.left;
+        dims.top = bcrect.top;
+        dims.right = bcrect.right;
+        dims.bottom = bcrect.bottom;
+
+        if(bcrect.width)
+        {
+            dims.width = bcrect.width;
+            dims.height = bcrect.height;
+        }
+        else
+        {
+            dims.width = dims.right - dims.left;
+            dims.height = dims.bottom - dims.top;
+        }
+    }
+    return dims;
+};
+
+this.applyBodyOffsets = function(elt, clientRect)
+{
+    var od = elt.ownerDocument;
+    if (!od.body)
+        return clientRect;
+
+    var style = od.defaultView.getComputedStyle(od.body, null);
+
+    var pos = style.getPropertyValue('position');
+    if(pos === 'absolute' || pos === 'relative')
+    {
+        var borderLeft = parseInt(style.getPropertyValue('border-left-width').replace('px', ''),10) || 0;
+        var borderTop = parseInt(style.getPropertyValue('border-top-width').replace('px', ''),10) || 0;
+        var paddingLeft = parseInt(style.getPropertyValue('padding-left').replace('px', ''),10) || 0;
+        var paddingTop = parseInt(style.getPropertyValue('padding-top').replace('px', ''),10) || 0;
+        var marginLeft = parseInt(style.getPropertyValue('margin-left').replace('px', ''),10) || 0;
+        var marginTop = parseInt(style.getPropertyValue('margin-top').replace('px', ''),10) || 0;
+
+        var offsetX = borderLeft + paddingLeft + marginLeft;
+        var offsetY = borderTop + paddingTop + marginTop;
+
+        clientRect.left -= offsetX;
+        clientRect.top -= offsetY;
+        clientRect.right -= offsetX;
+        clientRect.bottom -= offsetY;
+    }
+
+    return clientRect;
+};
+
+this.getOffsetSize = function(elt)
+{
+    return {width: elt.offsetWidth, height: elt.offsetHeight};
+};
+
+this.getOverflowParent = function(element)
+{
+    for (var scrollParent = element.parentNode; scrollParent; scrollParent = scrollParent.offsetParent)
+    {
+        if (scrollParent.scrollHeight > scrollParent.offsetHeight)
+            return scrollParent;
+    }
+};
+
+this.isScrolledToBottom = function(element)
+{
+    var onBottom = (element.scrollTop + element.offsetHeight) == element.scrollHeight;
+    if (FBTrace.DBG_CONSOLE)
+        FBTrace.sysout("isScrolledToBottom offsetHeight: "+element.offsetHeight +" onBottom:"+onBottom);
+    return onBottom;
+};
+
+this.scrollToBottom = function(element)
+{
+        element.scrollTop = element.scrollHeight;
+
+        if (FBTrace.DBG_CONSOLE)
+        {
+            FBTrace.sysout("scrollToBottom reset scrollTop "+element.scrollTop+" = "+element.scrollHeight);
+            if (element.scrollHeight == element.offsetHeight)
+                FBTrace.sysout("scrollToBottom attempt to scroll non-scrollable element "+element, element);
+        }
+
+        return (element.scrollTop == element.scrollHeight);
+};
+
+this.move = function(element, x, y)
+{
+    element.style.left = x + "px";
+    element.style.top = y + "px";
+};
+
+this.resize = function(element, w, h)
+{
+    element.style.width = w + "px";
+    element.style.height = h + "px";
+};
+
+this.linesIntoCenterView = function(element, scrollBox)  // {before: int, after: int}
+{
+    if (!scrollBox)
+        scrollBox = this.getOverflowParent(element);
+
+    if (!scrollBox)
+        return;
+
+    var offset = this.getClientOffset(element);
+
+    var topSpace = offset.y - scrollBox.scrollTop;
+    var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight)
+            - (offset.y + element.offsetHeight);
+
+    if (topSpace < 0 || bottomSpace < 0)
+    {
+        var split = (scrollBox.clientHeight/2);
+        var centerY = offset.y - split;
+        scrollBox.scrollTop = centerY;
+        topSpace = split;
+        bottomSpace = split -  element.offsetHeight;
+    }
+
+    return {before: Math.round((topSpace/element.offsetHeight) + 0.5),
+            after: Math.round((bottomSpace/element.offsetHeight) + 0.5) };
+};
+
+this.scrollIntoCenterView = function(element, scrollBox, notX, notY)
+{
+    if (!element)
+        return;
+
+    if (!scrollBox)
+        scrollBox = this.getOverflowParent(element);
+
+    if (!scrollBox)
+        return;
+
+    var offset = this.getClientOffset(element);
+
+    if (!notY)
+    {
+        var topSpace = offset.y - scrollBox.scrollTop;
+        var bottomSpace = (scrollBox.scrollTop + scrollBox.clientHeight)
+            - (offset.y + element.offsetHeight);
+
+        if (topSpace < 0 || bottomSpace < 0)
+        {
+            var centerY = offset.y - (scrollBox.clientHeight/2);
+            scrollBox.scrollTop = centerY;
+        }
+    }
+
+    if (!notX)
+    {
+        var leftSpace = offset.x - scrollBox.scrollLeft;
+        var rightSpace = (scrollBox.scrollLeft + scrollBox.clientWidth)
+            - (offset.x + element.clientWidth);
+
+        if (leftSpace < 0 || rightSpace < 0)
+        {
+            var centerX = offset.x - (scrollBox.clientWidth/2);
+            scrollBox.scrollLeft = centerX;
+        }
+    }
+    if (FBTrace.DBG_SOURCEFILES)
+        FBTrace.sysout("lib.scrollIntoCenterView ","Element:"+element.innerHTML);
+};
+
+
+// ************************************************************************************************
+// CSS
+
+var cssKeywordMap = null;
+var cssPropNames = null;
+var cssColorNames = null;
+var imageRules = null;
+
+this.getCSSKeywordsByProperty = function(propName)
+{
+    if (!cssKeywordMap)
+    {
+        cssKeywordMap = {};
+
+        for (var name in this.cssInfo)
+        {
+            var list = [];
+
+            var types = this.cssInfo[name];
+            for (var i = 0; i < types.length; ++i)
+            {
+                var keywords = this.cssKeywords[types[i]];
+                if (keywords)
+                    list.push.apply(list, keywords);
+            }
+
+            cssKeywordMap[name] = list;
+        }
+    }
+
+    return propName in cssKeywordMap ? cssKeywordMap[propName] : [];
+};
+
+this.getCSSPropertyNames = function()
+{
+    if (!cssPropNames)
+    {
+        cssPropNames = [];
+
+        for (var name in this.cssInfo)
+            cssPropNames.push(name);
+    }
+
+    return cssPropNames;
+};
+
+this.isColorKeyword = function(keyword)
+{
+    if (keyword == "transparent")
+        return false;
+
+    if (!cssColorNames)
+    {
+        cssColorNames = [];
+
+        var colors = this.cssKeywords["color"];
+        for (var i = 0; i < colors.length; ++i)
+            cssColorNames.push(colors[i].toLowerCase());
+
+        var systemColors = this.cssKeywords["systemColor"];
+        for (var i = 0; i < systemColors.length; ++i)
+            cssColorNames.push(systemColors[i].toLowerCase());
+    }
+
+    return cssColorNames.indexOf ? // Array.indexOf is not available in IE
+            cssColorNames.indexOf(keyword.toLowerCase()) != -1 :
+            (" " + cssColorNames.join(" ") + " ").indexOf(" " + keyword.toLowerCase() + " ") != -1;
+};
+
+this.isImageRule = function(rule)
+{
+    if (!imageRules)
+    {
+        imageRules = [];
+
+        for (var i in this.cssInfo)
+        {
+            var r = i.toLowerCase();
+            var suffix = "image";
+            if (r.match(suffix + "$") == suffix || r == "background")
+                imageRules.push(r);
+        }
+    }
+
+    return imageRules.indexOf ? // Array.indexOf is not available in IE
+            imageRules.indexOf(rule.toLowerCase()) != -1 :
+            (" " + imageRules.join(" ") + " ").indexOf(" " + rule.toLowerCase() + " ") != -1;
+};
+
+this.copyTextStyles = function(fromNode, toNode, style)
+{
+    var view = this.isIE ?
+            fromNode.ownerDocument.parentWindow :
+            fromNode.ownerDocument.defaultView;
+
+    if (view)
+    {
+        if (!style)
+            style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, "");
+
+        toNode.style.fontFamily = style.fontFamily;
+
+        // TODO: xxxpedro need to create a FBL.getComputedStyle() because IE
+        // returns wrong computed styles for inherited properties (like font-*)
+        //
+        // Also would be good to create a FBL.getStyle()
+        toNode.style.fontSize = style.fontSize;
+        toNode.style.fontWeight = style.fontWeight;
+        toNode.style.fontStyle = style.fontStyle;
+
+        return style;
+    }
+};
+
+this.copyBoxStyles = function(fromNode, toNode, style)
+{
+    var view = this.isIE ?
+            fromNode.ownerDocument.parentWindow :
+            fromNode.ownerDocument.defaultView;
+
+    if (view)
+    {
+        if (!style)
+            style = this.isIE ? fromNode.currentStyle : view.getComputedStyle(fromNode, "");
+
+        toNode.style.marginTop = style.marginTop;
+        toNode.style.marginRight = style.marginRight;
+        toNode.style.marginBottom = style.marginBottom;
+        toNode.style.marginLeft = style.marginLeft;
+        toNode.style.borderTopWidth = style.borderTopWidth;
+        toNode.style.borderRightWidth = style.borderRightWidth;
+        toNode.style.borderBottomWidth = style.borderBottomWidth;
+        toNode.style.borderLeftWidth = style.borderLeftWidth;
+
+        return style;
+    }
+};
+
+this.readBoxStyles = function(style)
+{
+    var styleNames = {
+        "margin-top": "marginTop", "margin-right": "marginRight",
+        "margin-left": "marginLeft", "margin-bottom": "marginBottom",
+        "border-top-width": "borderTop", "border-right-width": "borderRight",
+        "border-left-width": "borderLeft", "border-bottom-width": "borderBottom",
+        "padding-top": "paddingTop", "padding-right": "paddingRight",
+        "padding-left": "paddingLeft", "padding-bottom": "paddingBottom",
+        "z-index": "zIndex"
+    };
+
+    var styles = {};
+    for (var styleName in styleNames)
+        styles[styleNames[styleName]] = parseInt(style.getPropertyCSSValue(styleName).cssText) || 0;
+    if (FBTrace.DBG_INSPECT)
+        FBTrace.sysout("readBoxStyles ", styles);
+    return styles;
+};
+
+this.getBoxFromStyles = function(style, element)
+{
+    var args = this.readBoxStyles(style);
+    args.width = element.offsetWidth
+        - (args.paddingLeft+args.paddingRight+args.borderLeft+args.borderRight);
+    args.height = element.offsetHeight
+        - (args.paddingTop+args.paddingBottom+args.borderTop+args.borderBottom);
+    return args;
+};
+
+this.getElementCSSSelector = function(element)
+{
+    var label = element.localName.toLowerCase();
+    if (element.id)
+        label += "#" + element.id;
+    if (element.hasAttribute("class"))
+        label += "." + element.getAttribute("class").split(" ")[0];
+
+    return label;
+};
+
+this.getURLForStyleSheet= function(styleSheet)
+{
+    //http://www.w3.org/TR/DOM-Level-2-Style/stylesheets.html#StyleSheets-StyleSheet. For inline style sheets, the value of this attribute is null.
+    return (styleSheet.href ? styleSheet.href : styleSheet.ownerNode.ownerDocument.URL);
+};
+
+this.getDocumentForStyleSheet = function(styleSheet)
+{
+    while (styleSheet.parentStyleSheet && !styleSheet.ownerNode)
+    {
+        styleSheet = styleSheet.parentStyleSheet;
+    }
+    if (styleSheet.ownerNode)
+      return styleSheet.ownerNode.ownerDocument;
+};
+
+/**
+ * Retrieves the instance number for a given style sheet. The instance number
+ * is sheet's index within the set of all other sheets whose URL is the same.
+ */
+this.getInstanceForStyleSheet = function(styleSheet, ownerDocument)
+{
+    // System URLs are always unique (or at least we are making this assumption)
+    if (FBL.isSystemStyleSheet(styleSheet))
+        return 0;
+
+    // ownerDocument is an optional hint for performance
+    if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: " + styleSheet.href + " " + styleSheet.media.mediaText + " " + (styleSheet.ownerNode && FBL.getElementXPath(styleSheet.ownerNode)), ownerDocument);
+    ownerDocument = ownerDocument || FBL.getDocumentForStyleSheet(styleSheet);
+
+    var ret = 0,
+        styleSheets = ownerDocument.styleSheets,
+        href = styleSheet.href;
+    for (var i = 0; i < styleSheets.length; i++)
+    {
+        var curSheet = styleSheets[i];
+        if (FBTrace.DBG_CSS) FBTrace.sysout("getInstanceForStyleSheet: compare href " + i + " " + curSheet.href + " " + curSheet.media.mediaText + " " + (curSheet.ownerNode && FBL.getElementXPath(curSheet.ownerNode)));
+        if (curSheet == styleSheet)
+            break;
+        if (curSheet.href == href)
+            ret++;
+    }
+    return ret;
+};
+
+// ************************************************************************************************
+// HTML and XML Serialization
+
+
+var getElementType = this.getElementType = function(node)
+{
+    if (isElementXUL(node))
+        return 'xul';
+    else if (isElementSVG(node))
+        return 'svg';
+    else if (isElementMathML(node))
+        return 'mathml';
+    else if (isElementXHTML(node))
+        return 'xhtml';
+    else if (isElementHTML(node))
+        return 'html';
+};
+
+var getElementSimpleType = this.getElementSimpleType = function(node)
+{
+    if (isElementSVG(node))
+        return 'svg';
+    else if (isElementMathML(node))
+        return 'mathml';
+    else
+        return 'html';
+};
+
+var isElementHTML = this.isElementHTML = function(node)
+{
+    return node.nodeName == node.nodeName.toUpperCase();
+};
+
+var isElementXHTML = this.isElementXHTML = function(node)
+{
+    return node.nodeName == node.nodeName.toLowerCase();
+};
+
+var isElementMathML = this.isElementMathML = function(node)
+{
+    return node.namespaceURI == 'http://www.w3.org/1998/Math/MathML';
+};
+
+var isElementSVG = this.isElementSVG = function(node)
+{
+    return node.namespaceURI == 'http://www.w3.org/2000/svg';
+};
+
+var isElementXUL = this.isElementXUL = function(node)
+{
+    return node instanceof XULElement;
+};
+
+this.isSelfClosing = function(element)
+{
+    if (isElementSVG(element) || isElementMathML(element))
+        return true;
+    var tag = element.localName.toLowerCase();
+    return (this.selfClosingTags.hasOwnProperty(tag));
+};
+
+this.getElementHTML = function(element)
+{
+    var self=this;
+    function toHTML(elt)
+    {
+        if (elt.nodeType == Node.ELEMENT_NODE)
+        {
+            if (unwrapObject(elt).firebugIgnore)
+                return;
+
+            html.push('<', elt.nodeName.toLowerCase());
+
+            for (var i = 0; i < elt.attributes.length; ++i)
+            {
+                var attr = elt.attributes[i];
+
+                // Hide attributes set by Firebug
+                if (attr.localName.indexOf("firebug-") == 0)
+                    continue;
+
+                // MathML
+                if (attr.localName.indexOf("-moz-math") == 0)
+                {
+                    // just hide for now
+                    continue;
+                }
+
+                html.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"');
+            }
+
+            if (elt.firstChild)
+            {
+                html.push('>');
+
+                var pureText=true;
+                for (var child = element.firstChild; child; child = child.nextSibling)
+                    pureText=pureText && (child.nodeType == Node.TEXT_NODE);
+
+                if (pureText)
+                    html.push(escapeForHtmlEditor(elt.textContent));
+                else {
+                    for (var child = elt.firstChild; child; child = child.nextSibling)
+                        toHTML(child);
+                }
+
+                html.push('</', elt.nodeName.toLowerCase(), '>');
+            }
+            else if (isElementSVG(elt) || isElementMathML(elt))
+            {
+                html.push('/>');
+            }
+            else if (self.isSelfClosing(elt))
+            {
+                html.push((isElementXHTML(elt))?'/>':'>');
+            }
+            else
+            {
+                html.push('></', elt.nodeName.toLowerCase(), '>');
+            }
+        }
+        else if (elt.nodeType == Node.TEXT_NODE)
+            html.push(escapeForTextNode(elt.textContent));
+        else if (elt.nodeType == Node.CDATA_SECTION_NODE)
+            html.push('<![CDATA[', elt.nodeValue, ']]>');
+        else if (elt.nodeType == Node.COMMENT_NODE)
+            html.push('<!--', elt.nodeValue, '-->');
+    }
+
+    var html = [];
+    toHTML(element);
+    return html.join("");
+};
+
+this.getElementXML = function(element)
+{
+    function toXML(elt)
+    {
+        if (elt.nodeType == Node.ELEMENT_NODE)
+        {
+            if (unwrapObject(elt).firebugIgnore)
+                return;
+
+            xml.push('<', elt.nodeName.toLowerCase());
+
+            for (var i = 0; i < elt.attributes.length; ++i)
+            {
+                var attr = elt.attributes[i];
+
+                // Hide attributes set by Firebug
+                if (attr.localName.indexOf("firebug-") == 0)
+                    continue;
+
+                // MathML
+                if (attr.localName.indexOf("-moz-math") == 0)
+                {
+                    // just hide for now
+                    continue;
+                }
+
+                xml.push(' ', attr.nodeName, '="', escapeForElementAttribute(attr.nodeValue),'"');
+            }
+
+            if (elt.firstChild)
+            {
+                xml.push('>');
+
+                for (var child = elt.firstChild; child; child = child.nextSibling)
+                    toXML(child);
+
+                xml.push('</', elt.nodeName.toLowerCase(), '>');
+            }
+            else
+                xml.push('/>');
+        }
+        else if (elt.nodeType == Node.TEXT_NODE)
+            xml.push(elt.nodeValue);
+        else if (elt.nodeType == Node.CDATA_SECTION_NODE)
+            xml.push('<![CDATA[', elt.nodeValue, ']]>');
+        else if (elt.nodeType == Node.COMMENT_NODE)
+            xml.push('<!--', elt.nodeValue, '-->');
+    }
+
+    var xml = [];
+    toXML(element);
+    return xml.join("");
+};
+
+
+// ************************************************************************************************
+// CSS classes
+
+this.hasClass = function(node, name) // className, className, ...
+{
+    // TODO: xxxpedro when lib.hasClass is called with more than 2 arguments?
+    // this function can be optimized a lot if assumed 2 arguments only,
+    // which seems to be what happens 99% of the time
+    if (arguments.length == 2)
+        return (' '+node.className+' ').indexOf(' '+name+' ') != -1;
+
+    if (!node || node.nodeType != 1)
+        return false;
+    else
+    {
+        for (var i=1; i<arguments.length; ++i)
+        {
+            var name = arguments[i];
+            var re = new RegExp("(^|\\s)"+name+"($|\\s)");
+            if (!re.exec(node.className))
+                return false;
+        }
+
+        return true;
+    }
+};
+
+this.old_hasClass = function(node, name) // className, className, ...
+{
+    if (!node || node.nodeType != 1)
+        return false;
+    else
+    {
+        for (var i=1; i<arguments.length; ++i)
+        {
+            var name = arguments[i];
+            var re = new RegExp("(^|\\s)"+name+"($|\\s)");
+            if (!re.exec(node.className))
+                return false;
+        }
+
+        return true;
+    }
+};
+
+this.setClass = function(node, name)
+{
+    if (node && (' '+node.className+' ').indexOf(' '+name+' ') == -1)
+    ///if (node && !this.hasClass(node, name))
+        node.className += " " + name;
+};
+
+this.getClassValue = function(node, name)
+{
+    var re = new RegExp(name+"-([^ ]+)");
+    var m = re.exec(node.className);
+    return m ? m[1] : "";
+};
+
+this.removeClass = function(node, name)
+{
+    if (node && node.className)
+    {
+        var index = node.className.indexOf(name);
+        if (index >= 0)
+        {
+            var size = name.length;
+            node.className = node.className.substr(0,index-1) + node.className.substr(index+size);
+        }
+    }
+};
+
+this.toggleClass = function(elt, name)
+{
+    if ((' '+elt.className+' ').indexOf(' '+name+' ') != -1)
+    ///if (this.hasClass(elt, name))
+        this.removeClass(elt, name);
+    else
+        this.setClass(elt, name);
+};
+
+this.setClassTimed = function(elt, name, context, timeout)
+{
+    if (!timeout)
+        timeout = 1300;
+
+    if (elt.__setClassTimeout)
+        context.clearTimeout(elt.__setClassTimeout);
+    else
+        this.setClass(elt, name);
+
+    elt.__setClassTimeout = context.setTimeout(function()
+    {
+        delete elt.__setClassTimeout;
+
+        FBL.removeClass(elt, name);
+    }, timeout);
+};
+
+this.cancelClassTimed = function(elt, name, context)
+{
+    if (elt.__setClassTimeout)
+    {
+        FBL.removeClass(elt, name);
+        context.clearTimeout(elt.__setClassTimeout);
+        delete elt.__setClassTimeout;
+    }
+};
+
+
+// ************************************************************************************************
+// DOM queries
+
+this.$ = function(id, doc)
+{
+    if (doc)
+        return doc.getElementById(id);
+    else
+    {
+        return FBL.Firebug.chrome.document.getElementById(id);
+    }
+};
+
+this.$$ = function(selector, doc)
+{
+    if (doc || !FBL.Firebug.chrome)
+        return FBL.Firebug.Selector(selector, doc);
+    else
+    {
+        return FBL.Firebug.Selector(selector, FBL.Firebug.chrome.document);
+    }
+};
+
+this.getChildByClass = function(node) // ,classname, classname, classname...
+{
+    for (var i = 1; i < arguments.length; ++i)
+    {
+        var className = arguments[i];
+        var child = node.firstChild;
+        node = null;
+        for (; child; child = child.nextSibling)
+        {
+            if (this.hasClass(child, className))
+            {
+                node = child;
+                break;
+            }
+        }
+    }
+
+    return node;
+};
+
+this.getAncestorByClass = function(node, className)
+{
+    for (var parent = node; parent; parent = parent.parentNode)
+    {
+        if (this.hasClass(parent, className))
+            return parent;
+    }
+
+    return null;
+};
+
+
+this.getElementsByClass = function(node, className)
+{
+    var result = [];
+
+    for (var child = node.firstChild; child; child = child.nextSibling)
+    {
+        if (this.hasClass(child, className))
+            result.push(child);
+    }
+
+    return result;
+};
+
+this.getElementByClass = function(node, className)  // className, className, ...
+{
+    var args = cloneArray(arguments); args.splice(0, 1);
+    for (var child = node.firstChild; child; child = child.nextSibling)
+    {
+        var args1 = cloneArray(args); args1.unshift(child);
+        if (FBL.hasClass.apply(null, args1))
+            return child;
+        else
+        {
+            var found = FBL.getElementByClass.apply(null, args1);
+            if (found)
+                return found;
+        }
+    }
+
+    return null;
+};
+
+this.isAncestor = function(node, potentialAncestor)
+{
+    for (var parent = node; parent; parent = parent.parentNode)
+    {
+        if (parent == potentialAncestor)
+            return true;
+    }
+
+    return false;
+};
+
+this.getNextElement = function(node)
+{
+    while (node && node.nodeType != 1)
+        node = node.nextSibling;
+
+    return node;
+};
+
+this.getPreviousElement = function(node)
+{
+    while (node && node.nodeType != 1)
+        node = node.previousSibling;
+
+    return node;
+};
+
+this.getBody = function(doc)
+{
+    if (doc.body)
+        return doc.body;
+
+    var body = doc.getElementsByTagName("body")[0];
+    if (body)
+        return body;
+
+    return doc.firstChild;  // For non-HTML docs
+};
+
+this.findNextDown = function(node, criteria)
+{
+    if (!node)
+        return null;
+
+    for (var child = node.firstChild; child; child = child.nextSibling)
+    {
+        if (criteria(child))
+            return child;
+
+        var next = this.findNextDown(child, criteria);
+        if (next)
+            return next;
+    }
+};
+
+this.findPreviousUp = function(node, criteria)
+{
+    if (!node)
+        return null;
+
+    for (var child = node.lastChild; child; child = child.previousSibling)
+    {
+        var next = this.findPreviousUp(child, criteria);
+        if (next)
+            return next;
+
+        if (criteria(child))
+            return child;
+    }
+};
+
+this.findNext = function(node, criteria, upOnly, maxRoot)
+{
+    if (!node)
+        return null;
+
+    if (!upOnly)
+    {
+        var next = this.findNextDown(node, criteria);
+        if (next)
+            return next;
+    }
+
+    for (var sib = node.nextSibling; sib; sib = sib.nextSibling)
+    {
+        if (criteria(sib))
+            return sib;
+
+        var next = this.findNextDown(sib, criteria);
+        if (next)
+            return next;
+    }
+
+    if (node.parentNode && node.parentNode != maxRoot)
+        return this.findNext(node.parentNode, criteria, true);
+};
+
+this.findPrevious = function(node, criteria, downOnly, maxRoot)
+{
+    if (!node)
+        return null;
+
+    for (var sib = node.previousSibling; sib; sib = sib.previousSibling)
+    {
+        var prev = this.findPreviousUp(sib, criteria);
+        if (prev)
+            return prev;
+
+        if (criteria(sib))
+            return sib;
+    }
+
+    if (!downOnly)
+    {
+        var next = this.findPreviousUp(node, criteria);
+        if (next)
+            return next;
+    }
+
+    if (node.parentNode && node.parentNode != maxRoot)
+    {
+        if (criteria(node.parentNode))
+            return node.parentNode;
+
+        return this.findPrevious(node.parentNode, criteria, true);
+    }
+};
+
+this.getNextByClass = function(root, state)
+{
+    var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); };
+    return this.findNext(root, iter);
+};
+
+this.getPreviousByClass = function(root, state)
+{
+    var iter = function iter(node) { return node.nodeType == 1 && FBL.hasClass(node, state); };
+    return this.findPrevious(root, iter);
+};
+
+this.isElement = function(o)
+{
+    try {
+        return o && this.instanceOf(o, "Element");
+    }
+    catch (ex) {
+        return false;
+    }
+};
+
+
+// ************************************************************************************************
+// DOM Modification
+
+// TODO: xxxpedro use doc fragments in Context API
+var appendFragment = null;
+
+this.appendInnerHTML = function(element, html, referenceElement)
+{
+    // if undefined, we must convert it to null otherwise it will throw an error in IE
+    // when executing element.insertBefore(firstChild, referenceElement)
+    referenceElement = referenceElement || null;
+
+    var doc = element.ownerDocument;
+
+    // doc.createRange not available in IE
+    if (doc.createRange)
+    {
+        var range = doc.createRange();  // a helper object
+        range.selectNodeContents(element); // the environment to interpret the html
+
+        var fragment = range.createContextualFragment(html);  // parse
+        var firstChild = fragment.firstChild;
+        element.insertBefore(fragment, referenceElement);
+    }
+    else
+    {
+        if (!appendFragment || appendFragment.ownerDocument != doc)
+            appendFragment = doc.createDocumentFragment();
+
+        var div = doc.createElement("div");
+        div.innerHTML = html;
+
+        var firstChild = div.firstChild;
+        while (div.firstChild)
+            appendFragment.appendChild(div.firstChild);
+
+        element.insertBefore(appendFragment, referenceElement);
+
+        div = null;
+    }
+
+    return firstChild;
+};
+
+
+// ************************************************************************************************
+// DOM creation
+
+this.createElement = function(tagName, properties)
+{
+    properties = properties || {};
+    var doc = properties.document || FBL.Firebug.chrome.document;
+
+    var element = doc.createElement(tagName);
+
+    for(var name in properties)
+    {
+        if (name != "document")
+        {
+            element[name] = properties[name];
+        }
+    }
+
+    return element;
+};
+
+this.createGlobalElement = function(tagName, properties)
+{
+    properties = properties || {};
+    var doc = FBL.Env.browser.document;
+
+    var element = this.NS && doc.createElementNS ?
+            doc.createElementNS(FBL.NS, tagName) :
+            doc.createElement(tagName);
+
+    for(var name in properties)
+    {
+        var propname = name;
+        if (FBL.isIE && name == "class") propname = "className";
+
+        if (name != "document")
+        {
+            element.setAttribute(propname, properties[name]);
+        }
+    }
+
+    return element;
+};
+
+//************************************************************************************************
+
+this.safeGetWindowLocation = function(window)
+{
+    try
+    {
+        if (window)
+        {
+            if (window.closed)
+                return "(window.closed)";
+            if ("location" in window)
+                return window.location+"";
+            else
+                return "(no window.location)";
+        }
+        else
+            return "(no context.window)";
+    }
+    catch(exc)
+    {
+        if (FBTrace.DBG_WINDOWS || FBTrace.DBG_ERRORS)
+            FBTrace.sysout("TabContext.getWindowLocation failed "+exc, exc);
+            FBTrace.sysout("TabContext.getWindowLocation failed window:", window);
+        return "(getWindowLocation: "+exc+")";
+    }
+};
+
+// ************************************************************************************************
+// Events
+
+this.isLeftClick = function(event)
+{
+    return (this.isIE && event.type != "click" && event.type != "dblclick" ?
+            event.button == 1 : // IE "click" and "dblclick" button model
+            event.button == 0) && // others
+        this.noKeyModifiers(event);
+};
+
+this.isMiddleClick = function(event)
+{
+    return (this.isIE && event.type != "click" && event.type != "dblclick" ?
+            event.button == 4 : // IE "click" and "dblclick" button model
+            event.button == 1) &&
+        this.noKeyModifiers(event);
+};
+
+this.isRightClick = function(event)
+{
+    return (this.isIE && event.type != "click" && event.type != "dblclick" ?
+            event.button == 2 : // IE "click" and "dblclick" button model
+            event.button == 2) &&
+        this.noKeyModifiers(event);
+};
+
+this.noKeyModifiers = function(event)
+{
+    return !event.ctrlKey && !event.shiftKey && !event.altKey && !event.metaKey;
+};
+
+this.isControlClick = function(event)
+{
+    return (this.isIE && event.type != "click" && event.type != "dblclick" ?
+            event.button == 1 : // IE "click" and "dblclick" button model
+            event.button == 0) &&
+        this.isControl(event);
+};
+
+this.isShiftClick = function(event)
+{
+    return (this.isIE && event.type != "click" && event.type != "dblclick" ?
+            event.button == 1 : // IE "click" and "dblclick" button model
+            event.button == 0) &&
+        this.isShift(event);
+};
+
+this.isControl = function(event)
+{
+    return (event.metaKey || event.ctrlKey) && !event.shiftKey && !event.altKey;
+};
+
+this.isAlt = function(event)
+{
+    return event.altKey && !event.ctrlKey && !event.shiftKey && !event.metaKey;
+};
+
+this.isAltClick = function(event)
+{
+    return (this.isIE && event.type != "click" && event.type != "dblclick" ?
+            event.button == 1 : // IE "click" and "dblclick" button model
+            event.button == 0) &&
+        this.isAlt(event);
+};
+
+this.isControlShift = function(event)
+{
+    return (event.metaKey || event.ctrlKey) && event.shiftKey && !event.altKey;
+};
+
+this.isShift = function(event)
+{
+    return event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey;
+};
+
+this.addEvent = function(object, name, handler, useCapture)
+{
+    if (object.addEventListener)
+        object.addEventListener(name, handler, useCapture);
+    else
+        object.attachEvent("on"+name, handler);
+};
+
+this.removeEvent = function(object, name, handler, useCapture)
+{
+    try
+    {
+        if (object.removeEventListener)
+            object.removeEventListener(name, handler, useCapture);
+        else
+            object.detachEvent("on"+name, handler);
+    }
+    catch(e)
+    {
+        if (FBTrace.DBG_ERRORS)
+            FBTrace.sysout("FBL.removeEvent error: ", object, name);
+    }
+};
+
+this.cancelEvent = function(e, preventDefault)
+{
+    if (!e) return;
+
+    if (preventDefault)
+    {
+                if (e.preventDefault)
+                    e.preventDefault();
+                else
+                    e.returnValue = false;
+    }
+
+    if (e.stopPropagation)
+        e.stopPropagation();
+    else
+        e.cancelBubble = true;
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.addGlobalEvent = function(name, handler)
+{
+    var doc = this.Firebug.browser.document;
+    var frames = this.Firebug.browser.window.frames;
+
+    this.addEvent(doc, name, handler);
+
+    if (this.Firebug.chrome.type == "popup")
+        this.addEvent(this.Firebug.chrome.document, name, handler);
+
+    for (var i = 0, frame; frame = frames[i]; i++)
+    {
+        try
+        {
+            this.addEvent(frame.document, name, handler);
+        }
+        catch(E)
+        {
+            // Avoid acess denied
+        }
+    }
+};
+
+this.removeGlobalEvent = function(name, handler)
+{
+    var doc = this.Firebug.browser.document;
+    var frames = this.Firebug.browser.window.frames;
+
+    this.removeEvent(doc, name, handler);
+
+    if (this.Firebug.chrome.type == "popup")
+        this.removeEvent(this.Firebug.chrome.document, name, handler);
+
+    for (var i = 0, frame; frame = frames[i]; i++)
+    {
+        try
+        {
+            this.removeEvent(frame.document, name, handler);
+        }
+        catch(E)
+        {
+            // Avoid acess denied
+        }
+    }
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.dispatch = function(listeners, name, args)
+{
+    if (!listeners) return;
+
+    try
+    {/**/
+        if (typeof listeners.length != "undefined")
+        {
+            if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to "+listeners.length+" listeners");
+
+            for (var i = 0; i < listeners.length; ++i)
+            {
+                var listener = listeners[i];
+                if ( listener[name] )
+                    listener[name].apply(listener, args);
+            }
+        }
+        else
+        {
+            if (FBTrace.DBG_DISPATCH) FBTrace.sysout("FBL.dispatch", name+" to listeners of an object");
+
+            for (var prop in listeners)
+            {
+                var listener = listeners[prop];
+                if ( listener[name] )
+                    listener[name].apply(listener, args);
+            }
+        }
+    }
+    catch (exc)
+    {
+        if (FBTrace.DBG_ERRORS)
+        {
+            FBTrace.sysout(" Exception in lib.dispatch "+ name, exc);
+            //FBTrace.dumpProperties(" Exception in lib.dispatch listener", listener);
+        }
+    }
+    /**/
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var disableTextSelectionHandler = function(event)
+{
+    FBL.cancelEvent(event, true);
+
+    return false;
+};
+
+this.disableTextSelection = function(e)
+{
+    if (typeof e.onselectstart != "undefined") // IE
+        this.addEvent(e, "selectstart", disableTextSelectionHandler);
+
+    else // others
+    {
+        e.style.cssText = "user-select: none; -khtml-user-select: none; -moz-user-select: none;";
+
+        // canceling the event in FF will prevent the menu popups to close when clicking over
+        // text-disabled elements
+        if (!this.isFirefox)
+            this.addEvent(e, "mousedown", disableTextSelectionHandler);
+    }
+
+    e.style.cursor = "default";
+};
+
+this.restoreTextSelection = function(e)
+{
+    if (typeof e.onselectstart != "undefined") // IE
+        this.removeEvent(e, "selectstart", disableTextSelectionHandler);
+
+    else // others
+    {
+        e.style.cssText = "cursor: default;";
+
+        // canceling the event in FF will prevent the menu popups to close when clicking over
+        // text-disabled elements
+        if (!this.isFirefox)
+            this.removeEvent(e, "mousedown", disableTextSelectionHandler);
+    }
+};
+
+// ************************************************************************************************
+// DOM Events
+
+var eventTypes =
+{
+    composition: [
+        "composition",
+        "compositionstart",
+        "compositionend" ],
+    contextmenu: [
+        "contextmenu" ],
+    drag: [
+        "dragenter",
+        "dragover",
+        "dragexit",
+        "dragdrop",
+        "draggesture" ],
+    focus: [
+        "focus",
+        "blur" ],
+    form: [
+        "submit",
+        "reset",
+        "change",
+        "select",
+        "input" ],
+    key: [
+        "keydown",
+        "keyup",
+        "keypress" ],
+    load: [
+        "load",
+        "beforeunload",
+        "unload",
+        "abort",
+        "error" ],
+    mouse: [
+        "mousedown",
+        "mouseup",
+        "click",
+        "dblclick",
+        "mouseover",
+        "mouseout",
+        "mousemove" ],
+    mutation: [
+        "DOMSubtreeModified",
+        "DOMNodeInserted",
+        "DOMNodeRemoved",
+        "DOMNodeRemovedFromDocument",
+        "DOMNodeInsertedIntoDocument",
+        "DOMAttrModified",
+        "DOMCharacterDataModified" ],
+    paint: [
+        "paint",
+        "resize",
+        "scroll" ],
+    scroll: [
+        "overflow",
+        "underflow",
+        "overflowchanged" ],
+    text: [
+        "text" ],
+    ui: [
+        "DOMActivate",
+        "DOMFocusIn",
+        "DOMFocusOut" ],
+    xul: [
+        "popupshowing",
+        "popupshown",
+        "popuphiding",
+        "popuphidden",
+        "close",
+        "command",
+        "broadcast",
+        "commandupdate" ]
+};
+
+this.getEventFamily = function(eventType)
+{
+    if (!this.families)
+    {
+        this.families = {};
+
+        for (var family in eventTypes)
+        {
+            var types = eventTypes[family];
+            for (var i = 0; i < types.length; ++i)
+                this.families[types[i]] = family;
+        }
+    }
+
+    return this.families[eventType];
+};
+
+
+// ************************************************************************************************
+// URLs
+
+this.getFileName = function(url)
+{
+    var split = this.splitURLBase(url);
+    return split.name;
+};
+
+this.splitURLBase = function(url)
+{
+    if (this.isDataURL(url))
+        return this.splitDataURL(url);
+    return this.splitURLTrue(url);
+};
+
+this.splitDataURL = function(url)
+{
+    var mark = url.indexOf(':', 3);
+    if (mark != 4)
+        return false;   //  the first 5 chars must be 'data:'
+
+    var point = url.indexOf(',', mark+1);
+    if (point < mark)
+        return false; // syntax error
+
+    var props = { encodedContent: url.substr(point+1) };
+
+    var metadataBuffer = url.substr(mark+1, point);
+    var metadata = metadataBuffer.split(';');
+    for (var i = 0; i < metadata.length; i++)
+    {
+        var nv = metadata[i].split('=');
+        if (nv.length == 2)
+            props[nv[0]] = nv[1];
+    }
+
+    // Additional Firebug-specific properties
+    if (props.hasOwnProperty('fileName'))
+    {
+         var caller_URL = decodeURIComponent(props['fileName']);
+         var caller_split = this.splitURLTrue(caller_URL);
+
+        if (props.hasOwnProperty('baseLineNumber'))  // this means it's probably an eval()
+        {
+            props['path'] = caller_split.path;
+            props['line'] = props['baseLineNumber'];
+            var hint = decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, "");
+            props['name'] =  'eval->'+hint;
+        }
+        else
+        {
+            props['name'] = caller_split.name;
+            props['path'] = caller_split.path;
+        }
+    }
+    else
+    {
+        if (!props.hasOwnProperty('path'))
+            props['path'] = "data:";
+        if (!props.hasOwnProperty('name'))
+            props['name'] =  decodeURIComponent(props['encodedContent'].substr(0,200)).replace(/\s*$/, "");
+    }
+
+    return props;
+};
+
+this.splitURLTrue = function(url)
+{
+    var m = reSplitFile.exec(url);
+    if (!m)
+        return {name: url, path: url};
+    else if (!m[2])
+        return {path: m[1], name: m[1]};
+    else
+        return {path: m[1], name: m[2]+m[3]};
+};
+
+this.getFileExtension = function(url)
+{
+    if (!url)
+        return null;
+
+    // Remove query string from the URL if any.
+    var queryString = url.indexOf("?");
+    if (queryString != -1)
+        url = url.substr(0, queryString);
+
+    // Now get the file extension.
+    var lastDot = url.lastIndexOf(".");
+    return url.substr(lastDot+1);
+};
+
+this.isSystemURL = function(url)
+{
+    if (!url) return true;
+    if (url.length == 0) return true;
+    if (url[0] == 'h') return false;
+    if (url.substr(0, 9) == "resource:")
+        return true;
+    else if (url.substr(0, 16) == "chrome://firebug")
+        return true;
+    else if (url  == "XPCSafeJSObjectWrapper.cpp")
+        return true;
+    else if (url.substr(0, 6) == "about:")
+        return true;
+    else if (url.indexOf("firebug-service.js") != -1)
+        return true;
+    else
+        return false;
+};
+
+this.isSystemPage = function(win)
+{
+    try
+    {
+        var doc = win.document;
+        if (!doc)
+            return false;
+
+        // Detect pages for pretty printed XML
+        if ((doc.styleSheets.length && doc.styleSheets[0].href
+                == "chrome://global/content/xml/XMLPrettyPrint.css")
+            || (doc.styleSheets.length > 1 && doc.styleSheets[1].href
+                == "chrome://browser/skin/feeds/subscribe.css"))
+            return true;
+
+        return FBL.isSystemURL(win.location.href);
+    }
+    catch (exc)
+    {
+        // Sometimes documents just aren't ready to be manipulated here, but don't let that
+        // gum up the works
+        ERROR("tabWatcher.isSystemPage document not ready:"+ exc);
+        return false;
+    }
+};
+
+this.isSystemStyleSheet = function(sheet)
+{
+    var href = sheet && sheet.href;
+    return href && FBL.isSystemURL(href);
+};
+
+this.getURIHost = function(uri)
+{
+    try
+    {
+        if (uri)
+            return uri.host;
+        else
+            return "";
+    }
+    catch (exc)
+    {
+        return "";
+    }
+};
+
+this.isLocalURL = function(url)
+{
+    if (url.substr(0, 5) == "file:")
+        return true;
+    else if (url.substr(0, 8) == "wyciwyg:")
+        return true;
+    else
+        return false;
+};
+
+this.isDataURL = function(url)
+{
+    return (url && url.substr(0,5) == "data:");
+};
+
+this.getLocalPath = function(url)
+{
+    if (this.isLocalURL(url))
+    {
+        var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+        var file = fileHandler.getFileFromURLSpec(url);
+        return file.path;
+    }
+};
+
+this.getURLFromLocalFile = function(file)
+{
+    var fileHandler = ioService.getProtocolHandler("file").QueryInterface(Ci.nsIFileProtocolHandler);
+    var URL = fileHandler.getURLSpecFromFile(file);
+    return URL;
+};
+
+this.getDataURLForContent = function(content, url)
+{
+    // data:text/javascript;fileName=x%2Cy.js;baseLineNumber=10,<the-url-encoded-data>
+    var uri = "data:text/html;";
+    uri += "fileName="+encodeURIComponent(url)+ ",";
+    uri += encodeURIComponent(content);
+    return uri;
+},
+
+this.getDomain = function(url)
+{
+    var m = /[^:]+:\/{1,3}([^\/]+)/.exec(url);
+    return m ? m[1] : "";
+};
+
+this.getURLPath = function(url)
+{
+    var m = /[^:]+:\/{1,3}[^\/]+(\/.*?)$/.exec(url);
+    return m ? m[1] : "";
+};
+
+this.getPrettyDomain = function(url)
+{
+    var m = /[^:]+:\/{1,3}(www\.)?([^\/]+)/.exec(url);
+    return m ? m[2] : "";
+};
+
+this.absoluteURL = function(url, baseURL)
+{
+    return this.absoluteURLWithDots(url, baseURL).replace("/./", "/", "g");
+};
+
+this.absoluteURLWithDots = function(url, baseURL)
+{
+    if (url[0] == "?")
+        return baseURL + url;
+
+    var reURL = /(([^:]+:)\/{1,2}[^\/]*)(.*?)$/;
+    var m = reURL.exec(url);
+    if (m)
+        return url;
+
+    var m = reURL.exec(baseURL);
+    if (!m)
+        return "";
+
+    var head = m[1];
+    var tail = m[3];
+    if (url.substr(0, 2) == "//")
+        return m[2] + url;
+    else if (url[0] == "/")
+    {
+        return head + url;
+    }
+    else if (tail[tail.length-1] == "/")
+        return baseURL + url;
+    else
+    {
+        var parts = tail.split("/");
+        return head + parts.slice(0, parts.length-1).join("/") + "/" + url;
+    }
+};
+
+this.normalizeURL = function(url)  // this gets called a lot, any performance improvement welcome
+{
+    if (!url)
+        return "";
+    // Replace one or more characters that are not forward-slash followed by /.., by space.
+    if (url.length < 255) // guard against monsters.
+    {
+        // Replace one or more characters that are not forward-slash followed by /.., by space.
+        url = url.replace(/[^\/]+\/\.\.\//, "", "g");
+        // Issue 1496, avoid #
+        url = url.replace(/#.*/,"");
+        // For some reason, JSDS reports file URLs like "file:/" instead of "file:///", so they
+        // don't match up with the URLs we get back from the DOM
+        url = url.replace(/file:\/([^\/])/g, "file:///$1");
+        if (url.indexOf('chrome:')==0)
+        {
+            var m = reChromeCase.exec(url);  // 1 is package name, 2 is path
+            if (m)
+            {
+                url = "chrome://"+m[1].toLowerCase()+"/"+m[2];
+            }
+        }
+    }
+    return url;
+};
+
+this.denormalizeURL = function(url)
+{
+    return url.replace(/file:\/\/\//g, "file:/");
+};
+
+this.parseURLParams = function(url)
+{
+    var q = url ? url.indexOf("?") : -1;
+    if (q == -1)
+        return [];
+
+    var search = url.substr(q+1);
+    var h = search.lastIndexOf("#");
+    if (h != -1)
+        search = search.substr(0, h);
+
+    if (!search)
+        return [];
+
+    return this.parseURLEncodedText(search);
+};
+
+this.parseURLEncodedText = function(text)
+{
+    var maxValueLength = 25000;
+
+    var params = [];
+
+    // Unescape '+' characters that are used to encode a space.
+    // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt
+    text = text.replace(/\+/g, " ");
+
+    var args = text.split("&");
+    for (var i = 0; i < args.length; ++i)
+    {
+        try {
+            var parts = args[i].split("=");
+            if (parts.length == 2)
+            {
+                if (parts[1].length > maxValueLength)
+                    parts[1] = this.$STR("LargeData");
+
+                params.push({name: decodeURIComponent(parts[0]), value: decodeURIComponent(parts[1])});
+            }
+            else
+                params.push({name: decodeURIComponent(parts[0]), value: ""});
+        }
+        catch (e)
+        {
+            if (FBTrace.DBG_ERRORS)
+            {
+                FBTrace.sysout("parseURLEncodedText EXCEPTION ", e);
+                FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]);
+            }
+        }
+    }
+
+    params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; });
+
+    return params;
+};
+
+// TODO: xxxpedro lib. why loops in domplate are requiring array in parameters
+// as in response/request headers and get/post parameters in Net module?
+this.parseURLParamsArray = function(url)
+{
+    var q = url ? url.indexOf("?") : -1;
+    if (q == -1)
+        return [];
+
+    var search = url.substr(q+1);
+    var h = search.lastIndexOf("#");
+    if (h != -1)
+        search = search.substr(0, h);
+
+    if (!search)
+        return [];
+
+    return this.parseURLEncodedTextArray(search);
+};
+
+this.parseURLEncodedTextArray = function(text)
+{
+    var maxValueLength = 25000;
+
+    var params = [];
+
+    // Unescape '+' characters that are used to encode a space.
+    // See section 2.2.in RFC 3986: http://www.ietf.org/rfc/rfc3986.txt
+    text = text.replace(/\+/g, " ");
+
+    var args = text.split("&");
+    for (var i = 0; i < args.length; ++i)
+    {
+        try {
+            var parts = args[i].split("=");
+            if (parts.length == 2)
+            {
+                if (parts[1].length > maxValueLength)
+                    parts[1] = this.$STR("LargeData");
+
+                params.push({name: decodeURIComponent(parts[0]), value: [decodeURIComponent(parts[1])]});
+            }
+            else
+                params.push({name: decodeURIComponent(parts[0]), value: [""]});
+        }
+        catch (e)
+        {
+            if (FBTrace.DBG_ERRORS)
+            {
+                FBTrace.sysout("parseURLEncodedText EXCEPTION ", e);
+                FBTrace.sysout("parseURLEncodedText EXCEPTION URI", args[i]);
+            }
+        }
+    }
+
+    params.sort(function(a, b) { return a.name <= b.name ? -1 : 1; });
+
+    return params;
+};
+
+this.reEncodeURL = function(file, text)
+{
+    var lines = text.split("\n");
+    var params = this.parseURLEncodedText(lines[lines.length-1]);
+
+    var args = [];
+    for (var i = 0; i < params.length; ++i)
+        args.push(encodeURIComponent(params[i].name)+"="+encodeURIComponent(params[i].value));
+
+    var url = file.href;
+    url += (url.indexOf("?") == -1 ? "?" : "&") + args.join("&");
+
+    return url;
+};
+
+this.getResource = function(aURL)
+{
+    try
+    {
+        var channel=ioService.newChannel(aURL,null,null);
+        var input=channel.open();
+        return FBL.readFromStream(input);
+    }
+    catch (e)
+    {
+        if (FBTrace.DBG_ERRORS)
+            FBTrace.sysout("lib.getResource FAILS for "+aURL, e);
+    }
+};
+
+this.parseJSONString = function(jsonString, originURL)
+{
+    // See if this is a Prototype style *-secure request.
+    var regex = new RegExp(/^\/\*-secure-([\s\S]*)\*\/\s*$/);
+    var matches = regex.exec(jsonString);
+
+    if (matches)
+    {
+        jsonString = matches[1];
+
+        if (jsonString[0] == "\\" && jsonString[1] == "n")
+            jsonString = jsonString.substr(2);
+
+        if (jsonString[jsonString.length-2] == "\\" && jsonString[jsonString.length-1] == "n")
+            jsonString = jsonString.substr(0, jsonString.length-2);
+    }
+
+    if (jsonString.indexOf("&&&START&&&"))
+    {
+        regex = new RegExp(/&&&START&&& (.+) &&&END&&&/);
+        matches = regex.exec(jsonString);
+        if (matches)
+            jsonString = matches[1];
+    }
+
+    // throw on the extra parentheses
+    jsonString = "(" + jsonString + ")";
+
+    ///var s = Components.utils.Sandbox(originURL);
+    var jsonObject = null;
+
+    try
+    {
+        ///jsonObject = Components.utils.evalInSandbox(jsonString, s);
+
+        //jsonObject = Firebug.context.eval(jsonString);
+        jsonObject = Firebug.context.evaluate(jsonString, null, null, function(){return null;});
+    }
+    catch(e)
+    {
+        /***
+        if (e.message.indexOf("is not defined"))
+        {
+            var parts = e.message.split(" ");
+            s[parts[0]] = function(str){ return str; };
+            try {
+                jsonObject = Components.utils.evalInSandbox(jsonString, s);
+            } catch(ex) {
+                if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER)
+                    FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e);
+                return null;
+            }
+        }
+        else
+        {/**/
+            if (FBTrace.DBG_ERRORS || FBTrace.DBG_JSONVIEWER)
+                FBTrace.sysout("jsonviewer.parseJSON EXCEPTION", e);
+            return null;
+        ///}
+    }
+
+    return jsonObject;
+};
+
+// ************************************************************************************************
+
+this.objectToString = function(object)
+{
+    try
+    {
+        return object+"";
+    }
+    catch (exc)
+    {
+        return null;
+    }
+};
+
+// ************************************************************************************************
+// Input Caret Position
+
+this.setSelectionRange = function(input, start, length)
+{
+    if (input.createTextRange)
+    {
+        var range = input.createTextRange();
+        range.moveStart("character", start);
+        range.moveEnd("character", length - input.value.length);
+        range.select();
+    }
+    else if (input.setSelectionRange)
+    {
+        input.setSelectionRange(start, length);
+        input.focus();
+    }
+};
+
+// ************************************************************************************************
+// Input Selection Start / Caret Position
+
+this.getInputSelectionStart = function(input)
+{
+    if (document.selection)
+    {
+        var range = input.ownerDocument.selection.createRange();
+        var text = range.text;
+
+        //console.log("range", range.text);
+
+        // if there is a selection, find the start position
+        if (text)
+        {
+            return input.value.indexOf(text);
+        }
+        // if there is no selection, find the caret position
+        else
+        {
+            range.moveStart("character", -input.value.length);
+
+            return range.text.length;
+        }
+    }
+    else if (typeof input.selectionStart != "undefined")
+        return input.selectionStart;
+
+    return 0;
+};
+
+// ************************************************************************************************
+// Opera Tab Fix
+
+function onOperaTabBlur(e)
+{
+    if (this.lastKey == 9)
+      this.focus();
+};
+
+function onOperaTabKeyDown(e)
+{
+    this.lastKey = e.keyCode;
+};
+
+function onOperaTabFocus(e)
+{
+    this.lastKey = null;
+};
+
+this.fixOperaTabKey = function(el)
+{
+    el.onfocus = onOperaTabFocus;
+    el.onblur = onOperaTabBlur;
+    el.onkeydown = onOperaTabKeyDown;
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.Property = function(object, name)
+{
+    this.object = object;
+    this.name = name;
+
+    this.getObject = function()
+    {
+        return object[name];
+    };
+};
+
+this.ErrorCopy = function(message)
+{
+    this.message = message;
+};
+
+function EventCopy(event)
+{
+    // Because event objects are destroyed arbitrarily by Gecko, we must make a copy of them to
+    // represent them long term in the inspector.
+    for (var name in event)
+    {
+        try {
+            this[name] = event[name];
+        } catch (exc) { }
+    }
+}
+
+this.EventCopy = EventCopy;
+
+
+// ************************************************************************************************
+// Type Checking
+
+var toString = Object.prototype.toString;
+var reFunction = /^\s*function(\s+[\w_$][\w\d_$]*)?\s*\(/;
+
+this.isArray = function(object) {
+    return toString.call(object) === '[object Array]';
+};
+
+this.isFunction = function(object) {
+    if (!object) return false;
+
+    try
+    {
+        // FIXME: xxxpedro this is failing in IE for the global "external" object
+        return toString.call(object) === "[object Function]" ||
+                this.isIE && typeof object != "string" && reFunction.test(""+object);
+    }
+    catch (E)
+    {
+        FBTrace.sysout("Lib.isFunction() failed for ", object);
+        return false;
+    }
+};
+
+
+// ************************************************************************************************
+// Instance Checking
+
+this.instanceOf = function(object, className)
+{
+    if (!object || typeof object != "object")
+        return false;
+
+    // Try to use the native instanceof operator. We can only use it when we know
+    // exactly the window where the object is located at
+    if (object.ownerDocument)
+    {
+        // find the correct window of the object
+        var win = object.ownerDocument.defaultView || object.ownerDocument.parentWindow;
+
+        // if the class is accessible in the window, uses the native instanceof operator
+        // if the instanceof evaluates to "true" we can assume it is a instance, but if it
+        // evaluates to "false" we must continue with the duck type detection below because
+        // the native object may be extended, thus breaking the instanceof result
+        // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended
+        if (className in win && object instanceof win[className])
+            return true;
+    }
+    // If the object doesn't have the ownerDocument property, we'll try to look at
+    // the current context's window
+    else
+    {
+        // TODO: xxxpedro context
+        // Since we're not using yet a Firebug.context, we'll just use the top window
+        // (browser) as a reference
+        var win = Firebug.browser.window;
+        if (className in win)
+            return object instanceof win[className];
+    }
+
+    // get the duck type model from the cache
+    var cache = instanceCheckMap[className];
+    if (!cache)
+        return false;
+
+    // starts the hacky duck type detection
+    for(var n in cache)
+    {
+        var obj = cache[n];
+        var type = typeof obj;
+        obj = type == "object" ? obj : [obj];
+
+        for(var name in obj)
+        {
+            // avoid problems with extended native objects
+            // See Issue 3524: Firebug Lite Style Panel doesn't work if the native Element is extended
+            if (!obj.hasOwnProperty(name))
+                continue;
+
+            var value = obj[name];
+
+            if( n == "property" && !(value in object) ||
+                n == "method" && !this.isFunction(object[value]) ||
+                n == "value" && (""+object[name]).toLowerCase() != (""+value).toLowerCase() )
+                    return false;
+        }
+    }
+
+    return true;
+};
+
+var instanceCheckMap =
+{
+    // DuckTypeCheck:
+    // {
+    //     property: ["window", "document"],
+    //     method: "setTimeout",
+    //     value: {nodeType: 1}
+    // },
+
+    Window:
+    {
+        property: ["window", "document"],
+        method: "setTimeout"
+    },
+
+    Document:
+    {
+        property: ["body", "cookie"],
+        method: "getElementById"
+    },
+
+    Node:
+    {
+        property: "ownerDocument",
+        method: "appendChild"
+    },
+
+    Element:
+    {
+        property: "tagName",
+        value: {nodeType: 1}
+    },
+
+    Location:
+    {
+        property: ["hostname", "protocol"],
+        method: "assign"
+    },
+
+    HTMLImageElement:
+    {
+        property: "useMap",
+        value:
+        {
+            nodeType: 1,
+            tagName: "img"
+        }
+    },
+
+    HTMLAnchorElement:
+    {
+        property: "hreflang",
+        value:
+        {
+            nodeType: 1,
+            tagName: "a"
+        }
+    },
+
+    HTMLInputElement:
+    {
+        property: "form",
+        value:
+        {
+            nodeType: 1,
+            tagName: "input"
+        }
+    },
+
+    HTMLButtonElement:
+    {
+        // ?
+    },
+
+    HTMLFormElement:
+    {
+        method: "submit",
+        value:
+        {
+            nodeType: 1,
+            tagName: "form"
+        }
+    },
+
+    HTMLBodyElement:
+    {
+
+    },
+
+    HTMLHtmlElement:
+    {
+
+    },
+
+    CSSStyleRule:
+    {
+        property: ["selectorText", "style"]
+    }
+
+};
+
+
+// ************************************************************************************************
+// DOM Constants
+
+/*
+
+Problems:
+
+  - IE does not have window.Node, window.Element, etc
+  - for (var name in Node.prototype) return nothing on FF
+
+*/
+
+
+var domMemberMap2 = {};
+
+var domMemberMap2Sandbox = null;
+
+var getDomMemberMap2 = function(name)
+{
+    if (!domMemberMap2Sandbox)
+    {
+        var doc = Firebug.chrome.document;
+        var frame = doc.createElement("iframe");
+
+        frame.id = "FirebugSandbox";
+        frame.style.display = "none";
+        frame.src = "about:blank";
+
+        doc.body.appendChild(frame);
+
+        domMemberMap2Sandbox = frame.window || frame.contentWindow;
+    }
+
+    var props = [];
+
+    //var object = domMemberMap2Sandbox[name];
+    //object = object.prototype || object;
+
+    var object = null;
+
+    if (name == "Window")
+        object = domMemberMap2Sandbox.window;
+
+    else if (name == "Document")
+        object = domMemberMap2Sandbox.document;
+
+    else if (name == "HTMLScriptElement")
+        object = domMemberMap2Sandbox.document.createElement("script");
+
+    else if (name == "HTMLAnchorElement")
+        object = domMemberMap2Sandbox.document.createElement("a");
+
+    else if (name.indexOf("Element") != -1)
+    {
+        object = domMemberMap2Sandbox.document.createElement("div");
+    }
+
+    if (object)
+    {
+        //object = object.prototype || object;
+
+        //props  = 'addEventListener,document,location,navigator,window'.split(',');
+
+        for (var n in object)
+          props.push(n);
+    }
+    /**/
+
+    return props;
+    return extendArray(props, domMemberMap[name]);
+};
+
+// xxxpedro experimental get DOM members
+this.getDOMMembers = function(object)
+{
+    if (!domMemberCache)
+    {
+        FBL.domMemberCache = domMemberCache = {};
+
+        for (var name in domMemberMap)
+        {
+            var builtins = getDomMemberMap2(name);
+            var cache = domMemberCache[name] = {};
+
+            /*
+            if (name.indexOf("Element") != -1)
+            {
+                this.append(cache, this.getDOMMembers("Node"));
+                this.append(cache, this.getDOMMembers("Element"));
+            }
+            /**/
+
+            for (var i = 0; i < builtins.length; ++i)
+                cache[builtins[i]] = i;
+        }
+    }
+
+    try
+    {
+        if (this.instanceOf(object, "Window"))
+            { return domMemberCache.Window; }
+        else if (this.instanceOf(object, "Document") || this.instanceOf(object, "XMLDocument"))
+            { return domMemberCache.Document; }
+        else if (this.instanceOf(object, "Location"))
+            { return domMemberCache.Location; }
+        else if (this.instanceOf(object, "HTMLImageElement"))
+            { return domMemberCache.HTMLImageElement; }
+        else if (this.instanceOf(object, "HTMLAnchorElement"))
+            { return domMemberCache.HTMLAnchorElement; }
+        else if (this.instanceOf(object, "HTMLInputElement"))
+            { return domMemberCache.HTMLInputElement; }
+        else if (this.instanceOf(object, "HTMLButtonElement"))
+            { return domMemberCache.HTMLButtonElement; }
+        else if (this.instanceOf(object, "HTMLFormElement"))
+            { return domMemberCache.HTMLFormElement; }
+        else if (this.instanceOf(object, "HTMLBodyElement"))
+            { return domMemberCache.HTMLBodyElement; }
+        else if (this.instanceOf(object, "HTMLHtmlElement"))
+            { return domMemberCache.HTMLHtmlElement; }
+        else if (this.instanceOf(object, "HTMLScriptElement"))
+            { return domMemberCache.HTMLScriptElement; }
+        else if (this.instanceOf(object, "HTMLTableElement"))
+            { return domMemberCache.HTMLTableElement; }
+        else if (this.instanceOf(object, "HTMLTableRowElement"))
+            { return domMemberCache.HTMLTableRowElement; }
+        else if (this.instanceOf(object, "HTMLTableCellElement"))
+            { return domMemberCache.HTMLTableCellElement; }
+        else if (this.instanceOf(object, "HTMLIFrameElement"))
+            { return domMemberCache.HTMLIFrameElement; }
+        else if (this.instanceOf(object, "SVGSVGElement"))
+            { return domMemberCache.SVGSVGElement; }
+        else if (this.instanceOf(object, "SVGElement"))
+            { return domMemberCache.SVGElement; }
+        else if (this.instanceOf(object, "Element"))
+            { return domMemberCache.Element; }
+        else if (this.instanceOf(object, "Text") || this.instanceOf(object, "CDATASection"))
+            { return domMemberCache.Text; }
+        else if (this.instanceOf(object, "Attr"))
+            { return domMemberCache.Attr; }
+        else if (this.instanceOf(object, "Node"))
+            { return domMemberCache.Node; }
+        else if (this.instanceOf(object, "Event") || this.instanceOf(object, "EventCopy"))
+            { return domMemberCache.Event; }
+        else
+            return {};
+    }
+    catch(E)
+    {
+        if (FBTrace.DBG_ERRORS)
+            FBTrace.sysout("lib.getDOMMembers FAILED ", E);
+
+        return {};
+    }
+};
+
+
+/*
+this.getDOMMembers = function(object)
+{
+    if (!domMemberCache)
+    {
+        domMemberCache = {};
+
+        for (var name in domMemberMap)
+        {
+            var builtins = domMemberMap[name];
+            var cache = domMemberCache[name] = {};
+
+            for (var i = 0; i < builtins.length; ++i)
+                cache[builtins[i]] = i;
+        }
+    }
+
+    try
+    {
+        if (this.instanceOf(object, "Window"))
+            { return domMemberCache.Window; }
+        else if (object instanceof Document || object instanceof XMLDocument)
+            { return domMemberCache.Document; }
+        else if (object instanceof Location)
+            { return domMemberCache.Location; }
+        else if (object instanceof HTMLImageElement)
+            { return domMemberCache.HTMLImageElement; }
+        else if (object instanceof HTMLAnchorElement)
+            { return domMemberCache.HTMLAnchorElement; }
+        else if (object instanceof HTMLInputElement)
+            { return domMemberCache.HTMLInputElement; }
+        else if (object instanceof HTMLButtonElement)
+            { return domMemberCache.HTMLButtonElement; }
+        else if (object instanceof HTMLFormElement)
+            { return domMemberCache.HTMLFormElement; }
+        else if (object instanceof HTMLBodyElement)
+            { return domMemberCache.HTMLBodyElement; }
+        else if (object instanceof HTMLHtmlElement)
+            { return domMemberCache.HTMLHtmlElement; }
+        else if (object instanceof HTMLScriptElement)
+            { return domMemberCache.HTMLScriptElement; }
+        else if (object instanceof HTMLTableElement)
+            { return domMemberCache.HTMLTableElement; }
+        else if (object instanceof HTMLTableRowElement)
+            { return domMemberCache.HTMLTableRowElement; }
+        else if (object instanceof HTMLTableCellElement)
+            { return domMemberCache.HTMLTableCellElement; }
+        else if (object instanceof HTMLIFrameElement)
+            { return domMemberCache.HTMLIFrameElement; }
+        else if (object instanceof SVGSVGElement)
+            { return domMemberCache.SVGSVGElement; }
+        else if (object instanceof SVGElement)
+            { return domMemberCache.SVGElement; }
+        else if (object instanceof Element)
+            { return domMemberCache.Element; }
+        else if (object instanceof Text || object instanceof CDATASection)
+            { return domMemberCache.Text; }
+        else if (object instanceof Attr)
+            { return domMemberCache.Attr; }
+        else if (object instanceof Node)
+            { return domMemberCache.Node; }
+        else if (object instanceof Event || object instanceof EventCopy)
+            { return domMemberCache.Event; }
+        else
+            return {};
+    }
+    catch(E)
+    {
+        return {};
+    }
+};
+/**/
+
+this.isDOMMember = function(object, propName)
+{
+    var members = this.getDOMMembers(object);
+    return members && propName in members;
+};
+
+var domMemberCache = null;
+var domMemberMap = {};
+
+domMemberMap.Window =
+[
+    "document",
+    "frameElement",
+
+    "innerWidth",
+    "innerHeight",
+    "outerWidth",
+    "outerHeight",
+    "screenX",
+    "screenY",
+    "pageXOffset",
+    "pageYOffset",
+    "scrollX",
+    "scrollY",
+    "scrollMaxX",
+    "scrollMaxY",
+
+    "status",
+    "defaultStatus",
+
+    "parent",
+    "opener",
+    "top",
+    "window",
+    "content",
+    "self",
+
+    "location",
+    "history",
+    "frames",
+    "navigator",
+    "screen",
+    "menubar",
+    "toolbar",
+    "locationbar",
+    "personalbar",
+    "statusbar",
+    "directories",
+    "scrollbars",
+    "fullScreen",
+    "netscape",
+    "java",
+    "console",
+    "Components",
+    "controllers",
+    "closed",
+    "crypto",
+    "pkcs11",
+
+    "name",
+    "property",
+    "length",
+
+    "sessionStorage",
+    "globalStorage",
+
+    "setTimeout",
+    "setInterval",
+    "clearTimeout",
+    "clearInterval",
+    "addEventListener",
+    "removeEventListener",
+    "dispatchEvent",
+    "getComputedStyle",
+    "captureEvents",
+    "releaseEvents",
+    "routeEvent",
+    "enableExternalCapture",
+    "disableExternalCapture",
+    "moveTo",
+    "moveBy",
+    "resizeTo",
+    "resizeBy",
+    "scroll",
+    "scrollTo",
+    "scrollBy",
+    "scrollByLines",
+    "scrollByPages",
+    "sizeToContent",
+    "setResizable",
+    "getSelection",
+    "open",
+    "openDialog",
+    "close",
+    "alert",
+    "confirm",
+    "prompt",
+    "dump",
+    "focus",
+    "blur",
+    "find",
+    "back",
+    "forward",
+    "home",
+    "stop",
+    "print",
+    "atob",
+    "btoa",
+    "updateCommands",
+    "XPCNativeWrapper",
+    "GeckoActiveXObject",
+    "applicationCache"      // FF3
+];
+
+domMemberMap.Location =
+[
+    "href",
+    "protocol",
+    "host",
+    "hostname",
+    "port",
+    "pathname",
+    "search",
+    "hash",
+
+    "assign",
+    "reload",
+    "replace"
+];
+
+domMemberMap.Node =
+[
+    "id",
+    "className",
+
+    "nodeType",
+    "tagName",
+    "nodeName",
+    "localName",
+    "prefix",
+    "namespaceURI",
+    "nodeValue",
+
+    "ownerDocument",
+    "parentNode",
+    "offsetParent",
+    "nextSibling",
+    "previousSibling",
+    "firstChild",
+    "lastChild",
+    "childNodes",
+    "attributes",
+
+    "dir",
+    "baseURI",
+    "textContent",
+    "innerHTML",
+
+    "addEventListener",
+    "removeEventListener",
+    "dispatchEvent",
+    "cloneNode",
+    "appendChild",
+    "insertBefore",
+    "replaceChild",
+    "removeChild",
+    "compareDocumentPosition",
+    "hasAttributes",
+    "hasChildNodes",
+    "lookupNamespaceURI",
+    "lookupPrefix",
+    "normalize",
+    "isDefaultNamespace",
+    "isEqualNode",
+    "isSameNode",
+    "isSupported",
+    "getFeature",
+    "getUserData",
+    "setUserData"
+];
+
+domMemberMap.Document = extendArray(domMemberMap.Node,
+[
+    "documentElement",
+    "body",
+    "title",
+    "location",
+    "referrer",
+    "cookie",
+    "contentType",
+    "lastModified",
+    "characterSet",
+    "inputEncoding",
+    "xmlEncoding",
+    "xmlStandalone",
+    "xmlVersion",
+    "strictErrorChecking",
+    "documentURI",
+    "URL",
+
+    "defaultView",
+    "doctype",
+    "implementation",
+    "styleSheets",
+    "images",
+    "links",
+    "forms",
+    "anchors",
+    "embeds",
+    "plugins",
+    "applets",
+
+    "width",
+    "height",
+
+    "designMode",
+    "compatMode",
+    "async",
+    "preferredStylesheetSet",
+
+    "alinkColor",
+    "linkColor",
+    "vlinkColor",
+    "bgColor",
+    "fgColor",
+    "domain",
+
+    "addEventListener",
+    "removeEventListener",
+    "dispatchEvent",
+    "captureEvents",
+    "releaseEvents",
+    "routeEvent",
+    "clear",
+    "open",
+    "close",
+    "execCommand",
+    "execCommandShowHelp",
+    "getElementsByName",
+    "getSelection",
+    "queryCommandEnabled",
+    "queryCommandIndeterm",
+    "queryCommandState",
+    "queryCommandSupported",
+    "queryCommandText",
+    "queryCommandValue",
+    "write",
+    "writeln",
+    "adoptNode",
+    "appendChild",
+    "removeChild",
+    "renameNode",
+    "cloneNode",
+    "compareDocumentPosition",
+    "createAttribute",
+    "createAttributeNS",
+    "createCDATASection",
+    "createComment",
+    "createDocumentFragment",
+    "createElement",
+    "createElementNS",
+    "createEntityReference",
+    "createEvent",
+    "createExpression",
+    "createNSResolver",
+    "createNodeIterator",
+    "createProcessingInstruction",
+    "createRange",
+    "createTextNode",
+    "createTreeWalker",
+    "domConfig",
+    "evaluate",
+    "evaluateFIXptr",
+    "evaluateXPointer",
+    "getAnonymousElementByAttribute",
+    "getAnonymousNodes",
+    "addBinding",
+    "removeBinding",
+    "getBindingParent",
+    "getBoxObjectFor",
+    "setBoxObjectFor",
+    "getElementById",
+    "getElementsByTagName",
+    "getElementsByTagNameNS",
+    "hasAttributes",
+    "hasChildNodes",
+    "importNode",
+    "insertBefore",
+    "isDefaultNamespace",
+    "isEqualNode",
+    "isSameNode",
+    "isSupported",
+    "load",
+    "loadBindingDocument",
+    "lookupNamespaceURI",
+    "lookupPrefix",
+    "normalize",
+    "normalizeDocument",
+    "getFeature",
+    "getUserData",
+    "setUserData"
+]);
+
+domMemberMap.Element = extendArray(domMemberMap.Node,
+[
+    "clientWidth",
+    "clientHeight",
+    "offsetLeft",
+    "offsetTop",
+    "offsetWidth",
+    "offsetHeight",
+    "scrollLeft",
+    "scrollTop",
+    "scrollWidth",
+    "scrollHeight",
+
+    "style",
+
+    "tabIndex",
+    "title",
+    "lang",
+    "align",
+    "spellcheck",
+
+    "addEventListener",
+    "removeEventListener",
+    "dispatchEvent",
+    "focus",
+    "blur",
+    "cloneNode",
+    "appendChild",
+    "insertBefore",
+    "replaceChild",
+    "removeChild",
+    "compareDocumentPosition",
+    "getElementsByTagName",
+    "getElementsByTagNameNS",
+    "getAttribute",
+    "getAttributeNS",
+    "getAttributeNode",
+    "getAttributeNodeNS",
+    "setAttribute",
+    "setAttributeNS",
+    "setAttributeNode",
+    "setAttributeNodeNS",
+    "removeAttribute",
+    "removeAttributeNS",
+    "removeAttributeNode",
+    "hasAttribute",
+    "hasAttributeNS",
+    "hasAttributes",
+    "hasChildNodes",
+    "lookupNamespaceURI",
+    "lookupPrefix",
+    "normalize",
+    "isDefaultNamespace",
+    "isEqualNode",
+    "isSameNode",
+    "isSupported",
+    "getFeature",
+    "getUserData",
+    "setUserData"
+]);
+
+domMemberMap.SVGElement = extendArray(domMemberMap.Element,
+[
+    "x",
+    "y",
+    "width",
+    "height",
+    "rx",
+    "ry",
+    "transform",
+    "href",
+
+    "ownerSVGElement",
+    "viewportElement",
+    "farthestViewportElement",
+    "nearestViewportElement",
+
+    "getBBox",
+    "getCTM",
+    "getScreenCTM",
+    "getTransformToElement",
+    "getPresentationAttribute",
+    "preserveAspectRatio"
+]);
+
+domMemberMap.SVGSVGElement = extendArray(domMemberMap.Element,
+[
+    "x",
+    "y",
+    "width",
+    "height",
+    "rx",
+    "ry",
+    "transform",
+
+    "viewBox",
+    "viewport",
+    "currentView",
+    "useCurrentView",
+    "pixelUnitToMillimeterX",
+    "pixelUnitToMillimeterY",
+    "screenPixelToMillimeterX",
+    "screenPixelToMillimeterY",
+    "currentScale",
+    "currentTranslate",
+    "zoomAndPan",
+
+    "ownerSVGElement",
+    "viewportElement",
+    "farthestViewportElement",
+    "nearestViewportElement",
+    "contentScriptType",
+    "contentStyleType",
+
+    "getBBox",
+    "getCTM",
+    "getScreenCTM",
+    "getTransformToElement",
+    "getEnclosureList",
+    "getIntersectionList",
+    "getViewboxToViewportTransform",
+    "getPresentationAttribute",
+    "getElementById",
+    "checkEnclosure",
+    "checkIntersection",
+    "createSVGAngle",
+    "createSVGLength",
+    "createSVGMatrix",
+    "createSVGNumber",
+    "createSVGPoint",
+    "createSVGRect",
+    "createSVGString",
+    "createSVGTransform",
+    "createSVGTransformFromMatrix",
+    "deSelectAll",
+    "preserveAspectRatio",
+    "forceRedraw",
+    "suspendRedraw",
+    "unsuspendRedraw",
+    "unsuspendRedrawAll",
+    "getCurrentTime",
+    "setCurrentTime",
+    "animationsPaused",
+    "pauseAnimations",
+    "unpauseAnimations"
+]);
+
+domMemberMap.HTMLImageElement = extendArray(domMemberMap.Element,
+[
+    "src",
+    "naturalWidth",
+    "naturalHeight",
+    "width",
+    "height",
+    "x",
+    "y",
+    "name",
+    "alt",
+    "longDesc",
+    "lowsrc",
+    "border",
+    "complete",
+    "hspace",
+    "vspace",
+    "isMap",
+    "useMap"
+]);
+
+domMemberMap.HTMLAnchorElement = extendArray(domMemberMap.Element,
+[
+    "name",
+    "target",
+    "accessKey",
+    "href",
+    "protocol",
+    "host",
+    "hostname",
+    "port",
+    "pathname",
+    "search",
+    "hash",
+    "hreflang",
+    "coords",
+    "shape",
+    "text",
+    "type",
+    "rel",
+    "rev",
+    "charset"
+]);
+
+domMemberMap.HTMLIFrameElement = extendArray(domMemberMap.Element,
+[
+    "contentDocument",
+    "contentWindow",
+    "frameBorder",
+    "height",
+    "longDesc",
+    "marginHeight",
+    "marginWidth",
+    "name",
+    "scrolling",
+    "src",
+    "width"
+]);
+
+domMemberMap.HTMLTableElement = extendArray(domMemberMap.Element,
+[
+    "bgColor",
+    "border",
+    "caption",
+    "cellPadding",
+    "cellSpacing",
+    "frame",
+    "rows",
+    "rules",
+    "summary",
+    "tBodies",
+    "tFoot",
+    "tHead",
+    "width",
+
+    "createCaption",
+    "createTFoot",
+    "createTHead",
+    "deleteCaption",
+    "deleteRow",
+    "deleteTFoot",
+    "deleteTHead",
+    "insertRow"
+]);
+
+domMemberMap.HTMLTableRowElement = extendArray(domMemberMap.Element,
+[
+    "bgColor",
+    "cells",
+    "ch",
+    "chOff",
+    "rowIndex",
+    "sectionRowIndex",
+    "vAlign",
+
+    "deleteCell",
+    "insertCell"
+]);
+
+domMemberMap.HTMLTableCellElement = extendArray(domMemberMap.Element,
+[
+    "abbr",
+    "axis",
+    "bgColor",
+    "cellIndex",
+    "ch",
+    "chOff",
+    "colSpan",
+    "headers",
+    "height",
+    "noWrap",
+    "rowSpan",
+    "scope",
+    "vAlign",
+    "width"
+
+]);
+
+domMemberMap.HTMLScriptElement = extendArray(domMemberMap.Element,
+[
+    "src"
+]);
+
+domMemberMap.HTMLButtonElement = extendArray(domMemberMap.Element,
+[
+    "accessKey",
+    "disabled",
+    "form",
+    "name",
+    "type",
+    "value",
+
+    "click"
+]);
+
+domMemberMap.HTMLInputElement = extendArray(domMemberMap.Element,
+[
+    "type",
+    "value",
+    "checked",
+    "accept",
+    "accessKey",
+    "alt",
+    "controllers",
+    "defaultChecked",
+    "defaultValue",
+    "disabled",
+    "form",
+    "maxLength",
+    "name",
+    "readOnly",
+    "selectionEnd",
+    "selectionStart",
+    "size",
+    "src",
+    "textLength",
+    "useMap",
+
+    "click",
+    "select",
+    "setSelectionRange"
+]);
+
+domMemberMap.HTMLFormElement = extendArray(domMemberMap.Element,
+[
+    "acceptCharset",
+    "action",
+    "author",
+    "elements",
+    "encoding",
+    "enctype",
+    "entry_id",
+    "length",
+    "method",
+    "name",
+    "post",
+    "target",
+    "text",
+    "url",
+
+    "reset",
+    "submit"
+]);
+
+domMemberMap.HTMLBodyElement = extendArray(domMemberMap.Element,
+[
+    "aLink",
+    "background",
+    "bgColor",
+    "link",
+    "text",
+    "vLink"
+]);
+
+domMemberMap.HTMLHtmlElement = extendArray(domMemberMap.Element,
+[
+    "version"
+]);
+
+domMemberMap.Text = extendArray(domMemberMap.Node,
+[
+    "data",
+    "length",
+
+    "appendData",
+    "deleteData",
+    "insertData",
+    "replaceData",
+    "splitText",
+    "substringData"
+]);
+
+domMemberMap.Attr = extendArray(domMemberMap.Node,
+[
+    "name",
+    "value",
+    "specified",
+    "ownerElement"
+]);
+
+domMemberMap.Event =
+[
+    "type",
+    "target",
+    "currentTarget",
+    "originalTarget",
+    "explicitOriginalTarget",
+    "relatedTarget",
+    "rangeParent",
+    "rangeOffset",
+    "view",
+
+    "keyCode",
+    "charCode",
+    "screenX",
+    "screenY",
+    "clientX",
+    "clientY",
+    "layerX",
+    "layerY",
+    "pageX",
+    "pageY",
+
+    "detail",
+    "button",
+    "which",
+    "ctrlKey",
+    "shiftKey",
+    "altKey",
+    "metaKey",
+
+    "eventPhase",
+    "timeStamp",
+    "bubbles",
+    "cancelable",
+    "cancelBubble",
+
+    "isTrusted",
+    "isChar",
+
+    "getPreventDefault",
+    "initEvent",
+    "initMouseEvent",
+    "initKeyEvent",
+    "initUIEvent",
+    "preventBubble",
+    "preventCapture",
+    "preventDefault",
+    "stopPropagation"
+];
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.domConstantMap =
+{
+    "ELEMENT_NODE": 1,
+    "ATTRIBUTE_NODE": 1,
+    "TEXT_NODE": 1,
+    "CDATA_SECTION_NODE": 1,
+    "ENTITY_REFERENCE_NODE": 1,
+    "ENTITY_NODE": 1,
+    "PROCESSING_INSTRUCTION_NODE": 1,
+    "COMMENT_NODE": 1,
+    "DOCUMENT_NODE": 1,
+    "DOCUMENT_TYPE_NODE": 1,
+    "DOCUMENT_FRAGMENT_NODE": 1,
+    "NOTATION_NODE": 1,
+
+    "DOCUMENT_POSITION_DISCONNECTED": 1,
+    "DOCUMENT_POSITION_PRECEDING": 1,
+    "DOCUMENT_POSITION_FOLLOWING": 1,
+    "DOCUMENT_POSITION_CONTAINS": 1,
+    "DOCUMENT_POSITION_CONTAINED_BY": 1,
+    "DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC": 1,
+
+    "UNKNOWN_RULE": 1,
+    "STYLE_RULE": 1,
+    "CHARSET_RULE": 1,
+    "IMPORT_RULE": 1,
+    "MEDIA_RULE": 1,
+    "FONT_FACE_RULE": 1,
+    "PAGE_RULE": 1,
+
+    "CAPTURING_PHASE": 1,
+    "AT_TARGET": 1,
+    "BUBBLING_PHASE": 1,
+
+    "SCROLL_PAGE_UP": 1,
+    "SCROLL_PAGE_DOWN": 1,
+
+    "MOUSEUP": 1,
+    "MOUSEDOWN": 1,
+    "MOUSEOVER": 1,
+    "MOUSEOUT": 1,
+    "MOUSEMOVE": 1,
+    "MOUSEDRAG": 1,
+    "CLICK": 1,
+    "DBLCLICK": 1,
+    "KEYDOWN": 1,
+    "KEYUP": 1,
+    "KEYPRESS": 1,
+    "DRAGDROP": 1,
+    "FOCUS": 1,
+    "BLUR": 1,
+    "SELECT": 1,
+    "CHANGE": 1,
+    "RESET": 1,
+    "SUBMIT": 1,
+    "SCROLL": 1,
+    "LOAD": 1,
+    "UNLOAD": 1,
+    "XFER_DONE": 1,
+    "ABORT": 1,
+    "ERROR": 1,
+    "LOCATE": 1,
+    "MOVE": 1,
+    "RESIZE": 1,
+    "FORWARD": 1,
+    "HELP": 1,
+    "BACK": 1,
+    "TEXT": 1,
+
+    "ALT_MASK": 1,
+    "CONTROL_MASK": 1,
+    "SHIFT_MASK": 1,
+    "META_MASK": 1,
+
+    "DOM_VK_TAB": 1,
+    "DOM_VK_PAGE_UP": 1,
+    "DOM_VK_PAGE_DOWN": 1,
+    "DOM_VK_UP": 1,
+    "DOM_VK_DOWN": 1,
+    "DOM_VK_LEFT": 1,
+    "DOM_VK_RIGHT": 1,
+    "DOM_VK_CANCEL": 1,
+    "DOM_VK_HELP": 1,
+    "DOM_VK_BACK_SPACE": 1,
+    "DOM_VK_CLEAR": 1,
+    "DOM_VK_RETURN": 1,
+    "DOM_VK_ENTER": 1,
+    "DOM_VK_SHIFT": 1,
+    "DOM_VK_CONTROL": 1,
+    "DOM_VK_ALT": 1,
+    "DOM_VK_PAUSE": 1,
+    "DOM_VK_CAPS_LOCK": 1,
+    "DOM_VK_ESCAPE": 1,
+    "DOM_VK_SPACE": 1,
+    "DOM_VK_END": 1,
+    "DOM_VK_HOME": 1,
+    "DOM_VK_PRINTSCREEN": 1,
+    "DOM_VK_INSERT": 1,
+    "DOM_VK_DELETE": 1,
+    "DOM_VK_0": 1,
+    "DOM_VK_1": 1,
+    "DOM_VK_2": 1,
+    "DOM_VK_3": 1,
+    "DOM_VK_4": 1,
+    "DOM_VK_5": 1,
+    "DOM_VK_6": 1,
+    "DOM_VK_7": 1,
+    "DOM_VK_8": 1,
+    "DOM_VK_9": 1,
+    "DOM_VK_SEMICOLON": 1,
+    "DOM_VK_EQUALS": 1,
+    "DOM_VK_A": 1,
+    "DOM_VK_B": 1,
+    "DOM_VK_C": 1,
+    "DOM_VK_D": 1,
+    "DOM_VK_E": 1,
+    "DOM_VK_F": 1,
+    "DOM_VK_G": 1,
+    "DOM_VK_H": 1,
+    "DOM_VK_I": 1,
+    "DOM_VK_J": 1,
+    "DOM_VK_K": 1,
+    "DOM_VK_L": 1,
+    "DOM_VK_M": 1,
+    "DOM_VK_N": 1,
+    "DOM_VK_O": 1,
+    "DOM_VK_P": 1,
+    "DOM_VK_Q": 1,
+    "DOM_VK_R": 1,
+    "DOM_VK_S": 1,
+    "DOM_VK_T": 1,
+    "DOM_VK_U": 1,
+    "DOM_VK_V": 1,
+    "DOM_VK_W": 1,
+    "DOM_VK_X": 1,
+    "DOM_VK_Y": 1,
+    "DOM_VK_Z": 1,
+    "DOM_VK_CONTEXT_MENU": 1,
+    "DOM_VK_NUMPAD0": 1,
+    "DOM_VK_NUMPAD1": 1,
+    "DOM_VK_NUMPAD2": 1,
+    "DOM_VK_NUMPAD3": 1,
+    "DOM_VK_NUMPAD4": 1,
+    "DOM_VK_NUMPAD5": 1,
+    "DOM_VK_NUMPAD6": 1,
+    "DOM_VK_NUMPAD7": 1,
+    "DOM_VK_NUMPAD8": 1,
+    "DOM_VK_NUMPAD9": 1,
+    "DOM_VK_MULTIPLY": 1,
+    "DOM_VK_ADD": 1,
+    "DOM_VK_SEPARATOR": 1,
+    "DOM_VK_SUBTRACT": 1,
+    "DOM_VK_DECIMAL": 1,
+    "DOM_VK_DIVIDE": 1,
+    "DOM_VK_F1": 1,
+    "DOM_VK_F2": 1,
+    "DOM_VK_F3": 1,
+    "DOM_VK_F4": 1,
+    "DOM_VK_F5": 1,
+    "DOM_VK_F6": 1,
+    "DOM_VK_F7": 1,
+    "DOM_VK_F8": 1,
+    "DOM_VK_F9": 1,
+    "DOM_VK_F10": 1,
+    "DOM_VK_F11": 1,
+    "DOM_VK_F12": 1,
+    "DOM_VK_F13": 1,
+    "DOM_VK_F14": 1,
+    "DOM_VK_F15": 1,
+    "DOM_VK_F16": 1,
+    "DOM_VK_F17": 1,
+    "DOM_VK_F18": 1,
+    "DOM_VK_F19": 1,
+    "DOM_VK_F20": 1,
+    "DOM_VK_F21": 1,
+    "DOM_VK_F22": 1,
+    "DOM_VK_F23": 1,
+    "DOM_VK_F24": 1,
+    "DOM_VK_NUM_LOCK": 1,
+    "DOM_VK_SCROLL_LOCK": 1,
+    "DOM_VK_COMMA": 1,
+    "DOM_VK_PERIOD": 1,
+    "DOM_VK_SLASH": 1,
+    "DOM_VK_BACK_QUOTE": 1,
+    "DOM_VK_OPEN_BRACKET": 1,
+    "DOM_VK_BACK_SLASH": 1,
+    "DOM_VK_CLOSE_BRACKET": 1,
+    "DOM_VK_QUOTE": 1,
+    "DOM_VK_META": 1,
+
+    "SVG_ZOOMANDPAN_DISABLE": 1,
+    "SVG_ZOOMANDPAN_MAGNIFY": 1,
+    "SVG_ZOOMANDPAN_UNKNOWN": 1
+};
+
+this.cssInfo =
+{
+    "background": ["bgRepeat", "bgAttachment", "bgPosition", "color", "systemColor", "none"],
+    "background-attachment": ["bgAttachment"],
+    "background-color": ["color", "systemColor"],
+    "background-image": ["none"],
+    "background-position": ["bgPosition"],
+    "background-repeat": ["bgRepeat"],
+
+    "border": ["borderStyle", "thickness", "color", "systemColor", "none"],
+    "border-top": ["borderStyle", "borderCollapse", "color", "systemColor", "none"],
+    "border-right": ["borderStyle", "borderCollapse", "color", "systemColor", "none"],
+    "border-bottom": ["borderStyle", "borderCollapse", "color", "systemColor", "none"],
+    "border-left": ["borderStyle", "borderCollapse", "color", "systemColor", "none"],
+    "border-collapse": ["borderCollapse"],
+    "border-color": ["color", "systemColor"],
+    "border-top-color": ["color", "systemColor"],
+    "border-right-color": ["color", "systemColor"],
+    "border-bottom-color": ["color", "systemColor"],
+    "border-left-color": ["color", "systemColor"],
+    "border-spacing": [],
+    "border-style": ["borderStyle"],
+    "border-top-style": ["borderStyle"],
+    "border-right-style": ["borderStyle"],
+    "border-bottom-style": ["borderStyle"],
+    "border-left-style": ["borderStyle"],
+    "border-width": ["thickness"],
+    "border-top-width": ["thickness"],
+    "border-right-width": ["thickness"],
+    "border-bottom-width": ["thickness"],
+    "border-left-width": ["thickness"],
+
+    "bottom": ["auto"],
+    "caption-side": ["captionSide"],
+    "clear": ["clear", "none"],
+    "clip": ["auto"],
+    "color": ["color", "systemColor"],
+    "content": ["content"],
+    "counter-increment": ["none"],
+    "counter-reset": ["none"],
+    "cursor": ["cursor", "none"],
+    "direction": ["direction"],
+    "display": ["display", "none"],
+    "empty-cells": [],
+    "float": ["float", "none"],
+    "font": ["fontStyle", "fontVariant", "fontWeight", "fontFamily"],
+
+    "font-family": ["fontFamily"],
+    "font-size": ["fontSize"],
+    "font-size-adjust": [],
+    "font-stretch": [],
+    "font-style": ["fontStyle"],
+    "font-variant": ["fontVariant"],
+    "font-weight": ["fontWeight"],
+
+    "height": ["auto"],
+    "left": ["auto"],
+    "letter-spacing": [],
+    "line-height": [],
+
+    "list-style": ["listStyleType", "listStylePosition", "none"],
+    "list-style-image": ["none"],
+    "list-style-position": ["listStylePosition"],
+    "list-style-type": ["listStyleType", "none"],
+
+    "margin": [],
+    "margin-top": [],
+    "margin-right": [],
+    "margin-bottom": [],
+    "margin-left": [],
+
+    "marker-offset": ["auto"],
+    "min-height": ["none"],
+    "max-height": ["none"],
+    "min-width": ["none"],
+    "max-width": ["none"],
+
+    "outline": ["borderStyle", "color", "systemColor", "none"],
+    "outline-color": ["color", "systemColor"],
+    "outline-style": ["borderStyle"],
+    "outline-width": [],
+
+    "overflow": ["overflow", "auto"],
+    "overflow-x": ["overflow", "auto"],
+    "overflow-y": ["overflow", "auto"],
+
+    "padding": [],
+    "padding-top": [],
+    "padding-right": [],
+    "padding-bottom": [],
+    "padding-left": [],
+
+    "position": ["position"],
+    "quotes": ["none"],
+    "right": ["auto"],
+    "table-layout": ["tableLayout", "auto"],
+    "text-align": ["textAlign"],
+    "text-decoration": ["textDecoration", "none"],
+    "text-indent": [],
+    "text-shadow": [],
+    "text-transform": ["textTransform", "none"],
+    "top": ["auto"],
+    "unicode-bidi": [],
+    "vertical-align": ["verticalAlign"],
+    "white-space": ["whiteSpace"],
+    "width": ["auto"],
+    "word-spacing": [],
+    "z-index": [],
+
+    "-moz-appearance": ["mozAppearance"],
+    "-moz-border-radius": [],
+    "-moz-border-radius-bottomleft": [],
+    "-moz-border-radius-bottomright": [],
+    "-moz-border-radius-topleft": [],
+    "-moz-border-radius-topright": [],
+    "-moz-border-top-colors": ["color", "systemColor"],
+    "-moz-border-right-colors": ["color", "systemColor"],
+    "-moz-border-bottom-colors": ["color", "systemColor"],
+    "-moz-border-left-colors": ["color", "systemColor"],
+    "-moz-box-align": ["mozBoxAlign"],
+    "-moz-box-direction": ["mozBoxDirection"],
+    "-moz-box-flex": [],
+    "-moz-box-ordinal-group": [],
+    "-moz-box-orient": ["mozBoxOrient"],
+    "-moz-box-pack": ["mozBoxPack"],
+    "-moz-box-sizing": ["mozBoxSizing"],
+    "-moz-opacity": [],
+    "-moz-user-focus": ["userFocus", "none"],
+    "-moz-user-input": ["userInput"],
+    "-moz-user-modify": [],
+    "-moz-user-select": ["userSelect", "none"],
+    "-moz-background-clip": [],
+    "-moz-background-inline-policy": [],
+    "-moz-background-origin": [],
+    "-moz-binding": [],
+    "-moz-column-count": [],
+    "-moz-column-gap": [],
+    "-moz-column-width": [],
+    "-moz-image-region": []
+};
+
+this.inheritedStyleNames =
+{
+    "border-collapse": 1,
+    "border-spacing": 1,
+    "border-style": 1,
+    "caption-side": 1,
+    "color": 1,
+    "cursor": 1,
+    "direction": 1,
+    "empty-cells": 1,
+    "font": 1,
+    "font-family": 1,
+    "font-size-adjust": 1,
+    "font-size": 1,
+    "font-style": 1,
+    "font-variant": 1,
+    "font-weight": 1,
+    "letter-spacing": 1,
+    "line-height": 1,
+    "list-style": 1,
+    "list-style-image": 1,
+    "list-style-position": 1,
+    "list-style-type": 1,
+    "quotes": 1,
+    "text-align": 1,
+    "text-decoration": 1,
+    "text-indent": 1,
+    "text-shadow": 1,
+    "text-transform": 1,
+    "white-space": 1,
+    "word-spacing": 1
+};
+
+this.cssKeywords =
+{
+    "appearance":
+    [
+        "button",
+        "button-small",
+        "checkbox",
+        "checkbox-container",
+        "checkbox-small",
+        "dialog",
+        "listbox",
+        "menuitem",
+        "menulist",
+        "menulist-button",
+        "menulist-textfield",
+        "menupopup",
+        "progressbar",
+        "radio",
+        "radio-container",
+        "radio-small",
+        "resizer",
+        "scrollbar",
+        "scrollbarbutton-down",
+        "scrollbarbutton-left",
+        "scrollbarbutton-right",
+        "scrollbarbutton-up",
+        "scrollbartrack-horizontal",
+        "scrollbartrack-vertical",
+        "separator",
+        "statusbar",
+        "tab",
+        "tab-left-edge",
+        "tabpanels",
+        "textfield",
+        "toolbar",
+        "toolbarbutton",
+        "toolbox",
+        "tooltip",
+        "treeheadercell",
+        "treeheadersortarrow",
+        "treeitem",
+        "treetwisty",
+        "treetwistyopen",
+        "treeview",
+        "window"
+    ],
+
+    "systemColor":
+    [
+        "ActiveBorder",
+        "ActiveCaption",
+        "AppWorkspace",
+        "Background",
+        "ButtonFace",
+        "ButtonHighlight",
+        "ButtonShadow",
+        "ButtonText",
+        "CaptionText",
+        "GrayText",
+        "Highlight",
+        "HighlightText",
+        "InactiveBorder",
+        "InactiveCaption",
+        "InactiveCaptionText",
+        "InfoBackground",
+        "InfoText",
+        "Menu",
+        "MenuText",
+        "Scrollbar",
+        "ThreeDDarkShadow",
+        "ThreeDFace",
+        "ThreeDHighlight",
+        "ThreeDLightShadow",
+        "ThreeDShadow",
+        "Window",
+        "WindowFrame",
+        "WindowText",
+        "-moz-field",
+        "-moz-fieldtext",
+        "-moz-workspace",
+        "-moz-visitedhyperlinktext",
+        "-moz-use-text-color"
+    ],
+
+    "color":
+    [
+        "AliceBlue",
+        "AntiqueWhite",
+        "Aqua",
+        "Aquamarine",
+        "Azure",
+        "Beige",
+        "Bisque",
+        "Black",
+        "BlanchedAlmond",
+        "Blue",
+        "BlueViolet",
+        "Brown",
+        "BurlyWood",
+        "CadetBlue",
+        "Chartreuse",
+        "Chocolate",
+        "Coral",
+        "CornflowerBlue",
+        "Cornsilk",
+        "Crimson",
+        "Cyan",
+        "DarkBlue",
+        "DarkCyan",
+        "DarkGoldenRod",
+        "DarkGray",
+        "DarkGreen",
+        "DarkKhaki",
+        "DarkMagenta",
+        "DarkOliveGreen",
+        "DarkOrange",
+        "DarkOrchid",
+        "DarkRed",
+        "DarkSalmon",
+        "DarkSeaGreen",
+        "DarkSlateBlue",
+        "DarkSlateGray",
+        "DarkTurquoise",
+        "DarkViolet",
+        "DeepPink",
+        "DarkSkyBlue",
+        "DimGray",
+        "DodgerBlue",
+        "Feldspar",
+        "FireBrick",
+        "FloralWhite",
+        "ForestGreen",
+        "Fuchsia",
+        "Gainsboro",
+        "GhostWhite",
+        "Gold",
+        "GoldenRod",
+        "Gray",
+        "Green",
+        "GreenYellow",
+        "HoneyDew",
+        "HotPink",
+        "IndianRed",
+        "Indigo",
+        "Ivory",
+        "Khaki",
+        "Lavender",
+        "LavenderBlush",
+        "LawnGreen",
+        "LemonChiffon",
+        "LightBlue",
+        "LightCoral",
+        "LightCyan",
+        "LightGoldenRodYellow",
+        "LightGrey",
+        "LightGreen",
+        "LightPink",
+        "LightSalmon",
+        "LightSeaGreen",
+        "LightSkyBlue",
+        "LightSlateBlue",
+        "LightSlateGray",
+        "LightSteelBlue",
+        "LightYellow",
+        "Lime",
+        "LimeGreen",
+        "Linen",
+        "Magenta",
+        "Maroon",
+        "MediumAquaMarine",
+        "MediumBlue",
+        "MediumOrchid",
+        "MediumPurple",
+        "MediumSeaGreen",
+        "MediumSlateBlue",
+        "MediumSpringGreen",
+        "MediumTurquoise",
+        "MediumVioletRed",
+        "MidnightBlue",
+        "MintCream",
+        "MistyRose",
+        "Moccasin",
+        "NavajoWhite",
+        "Navy",
+        "OldLace",
+        "Olive",
+        "OliveDrab",
+        "Orange",
+        "OrangeRed",
+        "Orchid",
+        "PaleGoldenRod",
+        "PaleGreen",
+        "PaleTurquoise",
+        "PaleVioletRed",
+        "PapayaWhip",
+        "PeachPuff",
+        "Peru",
+        "Pink",
+        "Plum",
+        "PowderBlue",
+        "Purple",
+        "Red",
+        "RosyBrown",
+        "RoyalBlue",
+        "SaddleBrown",
+        "Salmon",
+        "SandyBrown",
+        "SeaGreen",
+        "SeaShell",
+        "Sienna",
+        "Silver",
+        "SkyBlue",
+        "SlateBlue",
+        "SlateGray",
+        "Snow",
+        "SpringGreen",
+        "SteelBlue",
+        "Tan",
+        "Teal",
+        "Thistle",
+        "Tomato",
+        "Turquoise",
+        "Violet",
+        "VioletRed",
+        "Wheat",
+        "White",
+        "WhiteSmoke",
+        "Yellow",
+        "YellowGreen",
+        "transparent",
+        "invert"
+    ],
+
+    "auto":
+    [
+        "auto"
+    ],
+
+    "none":
+    [
+        "none"
+    ],
+
+    "captionSide":
+    [
+        "top",
+        "bottom",
+        "left",
+        "right"
+    ],
+
+    "clear":
+    [
+        "left",
+        "right",
+        "both"
+    ],
+
+    "cursor":
+    [
+        "auto",
+        "cell",
+        "context-menu",
+        "crosshair",
+        "default",
+        "help",
+        "pointer",
+        "progress",
+        "move",
+        "e-resize",
+        "all-scroll",
+        "ne-resize",
+        "nw-resize",
+        "n-resize",
+        "se-resize",
+        "sw-resize",
+        "s-resize",
+        "w-resize",
+        "ew-resize",
+        "ns-resize",
+        "nesw-resize",
+        "nwse-resize",
+        "col-resize",
+        "row-resize",
+        "text",
+        "vertical-text",
+        "wait",
+        "alias",
+        "copy",
+        "move",
+        "no-drop",
+        "not-allowed",
+        "-moz-alias",
+        "-moz-cell",
+        "-moz-copy",
+        "-moz-grab",
+        "-moz-grabbing",
+        "-moz-contextmenu",
+        "-moz-zoom-in",
+        "-moz-zoom-out",
+        "-moz-spinning"
+    ],
+
+    "direction":
+    [
+        "ltr",
+        "rtl"
+    ],
+
+    "bgAttachment":
+    [
+        "scroll",
+        "fixed"
+    ],
+
+    "bgPosition":
+    [
+        "top",
+        "center",
+        "bottom",
+        "left",
+        "right"
+    ],
+
+    "bgRepeat":
+    [
+        "repeat",
+        "repeat-x",
+        "repeat-y",
+        "no-repeat"
+    ],
+
+    "borderStyle":
+    [
+        "hidden",
+        "dotted",
+        "dashed",
+        "solid",
+        "double",
+        "groove",
+        "ridge",
+        "inset",
+        "outset",
+        "-moz-bg-inset",
+        "-moz-bg-outset",
+        "-moz-bg-solid"
+    ],
+
+    "borderCollapse":
+    [
+        "collapse",
+        "separate"
+    ],
+
+    "overflow":
+    [
+        "visible",
+        "hidden",
+        "scroll",
+        "-moz-scrollbars-horizontal",
+        "-moz-scrollbars-none",
+        "-moz-scrollbars-vertical"
+    ],
+
+    "listStyleType":
+    [
+        "disc",
+        "circle",
+        "square",
+        "decimal",
+        "decimal-leading-zero",
+        "lower-roman",
+        "upper-roman",
+        "lower-greek",
+        "lower-alpha",
+        "lower-latin",
+        "upper-alpha",
+        "upper-latin",
+        "hebrew",
+        "armenian",
+        "georgian",
+        "cjk-ideographic",
+        "hiragana",
+        "katakana",
+        "hiragana-iroha",
+        "katakana-iroha",
+        "inherit"
+    ],
+
+    "listStylePosition":
+    [
+        "inside",
+        "outside"
+    ],
+
+    "content":
+    [
+        "open-quote",
+        "close-quote",
+        "no-open-quote",
+        "no-close-quote",
+        "inherit"
+    ],
+
+    "fontStyle":
+    [
+        "normal",
+        "italic",
+        "oblique",
+        "inherit"
+    ],
+
+    "fontVariant":
+    [
+        "normal",
+        "small-caps",
+        "inherit"
+    ],
+
+    "fontWeight":
+    [
+        "normal",
+        "bold",
+        "bolder",
+        "lighter",
+        "inherit"
+    ],
+
+    "fontSize":
+    [
+        "xx-small",
+        "x-small",
+        "small",
+        "medium",
+        "large",
+        "x-large",
+        "xx-large",
+        "smaller",
+        "larger"
+    ],
+
+    "fontFamily":
+    [
+        "Arial",
+        "Comic Sans MS",
+        "Georgia",
+        "Tahoma",
+        "Verdana",
+        "Times New Roman",
+        "Trebuchet MS",
+        "Lucida Grande",
+        "Helvetica",
+        "serif",
+        "sans-serif",
+        "cursive",
+        "fantasy",
+        "monospace",
+        "caption",
+        "icon",
+        "menu",
+        "message-box",
+        "small-caption",
+        "status-bar",
+        "inherit"
+    ],
+
+    "display":
+    [
+        "block",
+        "inline",
+        "inline-block",
+        "list-item",
+        "marker",
+        "run-in",
+        "compact",
+        "table",
+        "inline-table",
+        "table-row-group",
+        "table-column",
+        "table-column-group",
+        "table-header-group",
+        "table-footer-group",
+        "table-row",
+        "table-cell",
+        "table-caption",
+        "-moz-box",
+        "-moz-compact",
+        "-moz-deck",
+        "-moz-grid",
+        "-moz-grid-group",
+        "-moz-grid-line",
+        "-moz-groupbox",
+        "-moz-inline-block",
+        "-moz-inline-box",
+        "-moz-inline-grid",
+        "-moz-inline-stack",
+        "-moz-inline-table",
+        "-moz-marker",
+        "-moz-popup",
+        "-moz-runin",
+        "-moz-stack"
+    ],
+
+    "position":
+    [
+        "static",
+        "relative",
+        "absolute",
+        "fixed",
+        "inherit"
+    ],
+
+    "float":
+    [
+        "left",
+        "right"
+    ],
+
+    "textAlign":
+    [
+        "left",
+        "right",
+        "center",
+        "justify"
+    ],
+
+    "tableLayout":
+    [
+        "fixed"
+    ],
+
+    "textDecoration":
+    [
+        "underline",
+        "overline",
+        "line-through",
+        "blink"
+    ],
+
+    "textTransform":
+    [
+        "capitalize",
+        "lowercase",
+        "uppercase",
+        "inherit"
+    ],
+
+    "unicodeBidi":
+    [
+        "normal",
+        "embed",
+        "bidi-override"
+    ],
+
+    "whiteSpace":
+    [
+        "normal",
+        "pre",
+        "nowrap"
+    ],
+
+    "verticalAlign":
+    [
+        "baseline",
+        "sub",
+        "super",
+        "top",
+        "text-top",
+        "middle",
+        "bottom",
+        "text-bottom",
+        "inherit"
+    ],
+
+    "thickness":
+    [
+        "thin",
+        "medium",
+        "thick"
+    ],
+
+    "userFocus":
+    [
+        "ignore",
+        "normal"
+    ],
+
+    "userInput":
+    [
+        "disabled",
+        "enabled"
+    ],
+
+    "userSelect":
+    [
+        "normal"
+    ],
+
+    "mozBoxSizing":
+    [
+        "content-box",
+        "padding-box",
+        "border-box"
+    ],
+
+    "mozBoxAlign":
+    [
+        "start",
+        "center",
+        "end",
+        "baseline",
+        "stretch"
+    ],
+
+    "mozBoxDirection":
+    [
+        "normal",
+        "reverse"
+    ],
+
+    "mozBoxOrient":
+    [
+        "horizontal",
+        "vertical"
+    ],
+
+    "mozBoxPack":
+    [
+        "start",
+        "center",
+        "end"
+    ]
+};
+
+this.nonEditableTags =
+{
+    "HTML": 1,
+    "HEAD": 1,
+    "html": 1,
+    "head": 1
+};
+
+this.innerEditableTags =
+{
+    "BODY": 1,
+    "body": 1
+};
+
+this.selfClosingTags =
+{ // End tags for void elements are forbidden http://wiki.whatwg.org/wiki/HTML_vs._XHTML
+    "meta": 1,
+    "link": 1,
+    "area": 1,
+    "base": 1,
+    "col": 1,
+    "input": 1,
+    "img": 1,
+    "br": 1,
+    "hr": 1,
+    "param":1,
+    "embed":1
+};
+
+var invisibleTags = this.invisibleTags =
+{
+    "HTML": 1,
+    "HEAD": 1,
+    "TITLE": 1,
+    "META": 1,
+    "LINK": 1,
+    "STYLE": 1,
+    "SCRIPT": 1,
+    "NOSCRIPT": 1,
+    "BR": 1,
+    "PARAM": 1,
+    "COL": 1,
+
+    "html": 1,
+    "head": 1,
+    "title": 1,
+    "meta": 1,
+    "link": 1,
+    "style": 1,
+    "script": 1,
+    "noscript": 1,
+    "br": 1,
+    "param": 1,
+    "col": 1
+    /*
+    "window": 1,
+    "browser": 1,
+    "frame": 1,
+    "tabbrowser": 1,
+    "WINDOW": 1,
+    "BROWSER": 1,
+    "FRAME": 1,
+    "TABBROWSER": 1,
+    */
+};
+
+
+if (typeof KeyEvent == "undefined") {
+    this.KeyEvent = {
+        DOM_VK_CANCEL: 3,
+        DOM_VK_HELP: 6,
+        DOM_VK_BACK_SPACE: 8,
+        DOM_VK_TAB: 9,
+        DOM_VK_CLEAR: 12,
+        DOM_VK_RETURN: 13,
+        DOM_VK_ENTER: 14,
+        DOM_VK_SHIFT: 16,
+        DOM_VK_CONTROL: 17,
+        DOM_VK_ALT: 18,
+        DOM_VK_PAUSE: 19,
+        DOM_VK_CAPS_LOCK: 20,
+        DOM_VK_ESCAPE: 27,
+        DOM_VK_SPACE: 32,
+        DOM_VK_PAGE_UP: 33,
+        DOM_VK_PAGE_DOWN: 34,
+        DOM_VK_END: 35,
+        DOM_VK_HOME: 36,
+        DOM_VK_LEFT: 37,
+        DOM_VK_UP: 38,
+        DOM_VK_RIGHT: 39,
+        DOM_VK_DOWN: 40,
+        DOM_VK_PRINTSCREEN: 44,
+        DOM_VK_INSERT: 45,
+        DOM_VK_DELETE: 46,
+        DOM_VK_0: 48,
+        DOM_VK_1: 49,
+        DOM_VK_2: 50,
+        DOM_VK_3: 51,
+        DOM_VK_4: 52,
+        DOM_VK_5: 53,
+        DOM_VK_6: 54,
+        DOM_VK_7: 55,
+        DOM_VK_8: 56,
+        DOM_VK_9: 57,
+        DOM_VK_SEMICOLON: 59,
+        DOM_VK_EQUALS: 61,
+        DOM_VK_A: 65,
+        DOM_VK_B: 66,
+        DOM_VK_C: 67,
+        DOM_VK_D: 68,
+        DOM_VK_E: 69,
+        DOM_VK_F: 70,
+        DOM_VK_G: 71,
+        DOM_VK_H: 72,
+        DOM_VK_I: 73,
+        DOM_VK_J: 74,
+        DOM_VK_K: 75,
+        DOM_VK_L: 76,
+        DOM_VK_M: 77,
+        DOM_VK_N: 78,
+        DOM_VK_O: 79,
+        DOM_VK_P: 80,
+        DOM_VK_Q: 81,
+        DOM_VK_R: 82,
+        DOM_VK_S: 83,
+        DOM_VK_T: 84,
+        DOM_VK_U: 85,
+        DOM_VK_V: 86,
+        DOM_VK_W: 87,
+        DOM_VK_X: 88,
+        DOM_VK_Y: 89,
+        DOM_VK_Z: 90,
+        DOM_VK_CONTEXT_MENU: 93,
+        DOM_VK_NUMPAD0: 96,
+        DOM_VK_NUMPAD1: 97,
+        DOM_VK_NUMPAD2: 98,
+        DOM_VK_NUMPAD3: 99,
+        DOM_VK_NUMPAD4: 100,
+        DOM_VK_NUMPAD5: 101,
+        DOM_VK_NUMPAD6: 102,
+        DOM_VK_NUMPAD7: 103,
+        DOM_VK_NUMPAD8: 104,
+        DOM_VK_NUMPAD9: 105,
+        DOM_VK_MULTIPLY: 106,
+        DOM_VK_ADD: 107,
+        DOM_VK_SEPARATOR: 108,
+        DOM_VK_SUBTRACT: 109,
+        DOM_VK_DECIMAL: 110,
+        DOM_VK_DIVIDE: 111,
+        DOM_VK_F1: 112,
+        DOM_VK_F2: 113,
+        DOM_VK_F3: 114,
+        DOM_VK_F4: 115,
+        DOM_VK_F5: 116,
+        DOM_VK_F6: 117,
+        DOM_VK_F7: 118,
+        DOM_VK_F8: 119,
+        DOM_VK_F9: 120,
+        DOM_VK_F10: 121,
+        DOM_VK_F11: 122,
+        DOM_VK_F12: 123,
+        DOM_VK_F13: 124,
+        DOM_VK_F14: 125,
+        DOM_VK_F15: 126,
+        DOM_VK_F16: 127,
+        DOM_VK_F17: 128,
+        DOM_VK_F18: 129,
+        DOM_VK_F19: 130,
+        DOM_VK_F20: 131,
+        DOM_VK_F21: 132,
+        DOM_VK_F22: 133,
+        DOM_VK_F23: 134,
+        DOM_VK_F24: 135,
+        DOM_VK_NUM_LOCK: 144,
+        DOM_VK_SCROLL_LOCK: 145,
+        DOM_VK_COMMA: 188,
+        DOM_VK_PERIOD: 190,
+        DOM_VK_SLASH: 191,
+        DOM_VK_BACK_QUOTE: 192,
+        DOM_VK_OPEN_BRACKET: 219,
+        DOM_VK_BACK_SLASH: 220,
+        DOM_VK_CLOSE_BRACKET: 221,
+        DOM_VK_QUOTE: 222,
+        DOM_VK_META: 224
+    };
+}
+
+
+// ************************************************************************************************
+// Ajax
+
+/**
+ * @namespace
+ */
+this.Ajax =
+{
+
+    requests: [],
+    transport: null,
+    states: ["Uninitialized","Loading","Loaded","Interactive","Complete"],
+
+    initialize: function()
+    {
+        this.transport = FBL.getNativeXHRObject();
+    },
+
+    getXHRObject: function()
+    {
+        var xhrObj = false;
+        try
+        {
+            xhrObj = new XMLHttpRequest();
+        }
+        catch(e)
+        {
+            var progid = [
+                    "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0",
+                    "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"
+                ];
+
+            for ( var i=0; i < progid.length; ++i ) {
+                try
+                {
+                    xhrObj = new ActiveXObject(progid[i]);
+                }
+                catch(e)
+                {
+                    continue;
+                }
+                break;
+            }
+        }
+        finally
+        {
+            return xhrObj;
+        }
+    },
+
+
+    /**
+     * Create a AJAX request.
+     *
+     * @name request
+     * @param {Object}   options               request options
+     * @param {String}   options.url           URL to be requested
+     * @param {String}   options.type          Request type ("get" ou "post"). Default is "get".
+     * @param {Boolean}  options.async         Asynchronous flag. Default is "true".
+     * @param {String}   options.dataType      Data type ("text", "html", "xml" or "json"). Default is "text".
+     * @param {String}   options.contentType   Content-type of the data being sent. Default is "application/x-www-form-urlencoded".
+     * @param {Function} options.onLoading     onLoading callback
+     * @param {Function} options.onLoaded      onLoaded callback
+     * @param {Function} options.onInteractive onInteractive callback
+     * @param {Function} options.onComplete    onComplete callback
+     * @param {Function} options.onUpdate      onUpdate callback
+     * @param {Function} options.onSuccess     onSuccess callback
+     * @param {Function} options.onFailure     onFailure callback
+     */
+    request: function(options)
+    {
+        // process options
+        var o = FBL.extend(
+                {
+                    // default values
+                    type: "get",
+                    async: true,
+                    dataType: "text",
+                    contentType: "application/x-www-form-urlencoded"
+                },
+                options || {}
+            );
+
+        this.requests.push(o);
+
+        var s = this.getState();
+        if (s == "Uninitialized" || s == "Complete" || s == "Loaded")
+            this.sendRequest();
+    },
+
+    serialize: function(data)
+    {
+        var r = [""], rl = 0;
+        if (data) {
+            if (typeof data == "string")  r[rl++] = data;
+
+            else if (data.innerHTML && data.elements) {
+                for (var i=0,el,l=(el=data.elements).length; i < l; i++)
+                    if (el[i].name) {
+                        r[rl++] = encodeURIComponent(el[i].name);
+                        r[rl++] = "=";
+                        r[rl++] = encodeURIComponent(el[i].value);
+                        r[rl++] = "&";
+                    }
+
+            } else
+                for(var param in data) {
+                    r[rl++] = encodeURIComponent(param);
+                    r[rl++] = "=";
+                    r[rl++] = encodeURIComponent(data[param]);
+                    r[rl++] = "&";
+                }
+        }
+        return r.join("").replace(/&$/, "");
+    },
+
+    sendRequest: function()
+    {
+        var t = FBL.Ajax.transport, r = FBL.Ajax.requests.shift(), data;
+
+        // open XHR object
+        t.open(r.type, r.url, r.async);
+
+        //setRequestHeaders();
+
+        // indicates that it is a XHR request to the server
+        t.setRequestHeader("X-Requested-With", "XMLHttpRequest");
+
+        // if data is being sent, sets the appropriate content-type
+        if (data = FBL.Ajax.serialize(r.data))
+            t.setRequestHeader("Content-Type", r.contentType);
+
+        /** @ignore */
+        // onreadystatechange handler
+        t.onreadystatechange = function()
+        {
+            FBL.Ajax.onStateChange(r);
+        };
+
+        // send the request
+        t.send(data);
+    },
+
+    /**
+     * Handles the state change
+     */
+    onStateChange: function(options)
+    {
+        var fn, o = options, t = this.transport;
+        var state = this.getState(t);
+
+        if (fn = o["on" + state]) fn(this.getResponse(o), o);
+
+        if (state == "Complete")
+        {
+            var success = t.status == 200, response = this.getResponse(o);
+
+            if (fn = o["onUpdate"])
+              fn(response, o);
+
+            if (fn = o["on" + (success ? "Success" : "Failure")])
+              fn(response, o);
+
+            t.onreadystatechange = FBL.emptyFn;
+
+            if (this.requests.length > 0)
+                setTimeout(this.sendRequest, 10);
+        }
+    },
+
+    /**
+     * gets the appropriate response value according the type
+     */
+    getResponse: function(options)
+    {
+        var t = this.transport, type = options.dataType;
+
+        if      (t.status != 200) return t.statusText;
+        else if (type == "text")  return t.responseText;
+        else if (type == "html")  return t.responseText;
+        else if (type == "xml")   return t.responseXML;
+        else if (type == "json")  return eval("(" + t.responseText + ")");
+    },
+
+    /**
+     * returns the current state of the XHR object
+     */
+    getState: function()
+    {
+        return this.states[this.transport.readyState];
+    }
+
+};
+
+
+// ************************************************************************************************
+// Cookie, from http://www.quirksmode.org/js/cookies.html
+
+this.createCookie = function(name,value,days)
+{
+    if ('cookie' in document)
+    {
+        if (days)
+        {
+            var date = new Date();
+            date.setTime(date.getTime()+(days*24*60*60*1000));
+            var expires = "; expires="+date.toGMTString();
+        }
+        else
+            var expires = "";
+
+        document.cookie = name+"="+value+expires+"; path=/";
+    }
+};
+
+this.readCookie = function (name)
+{
+    if ('cookie' in document)
+    {
+        var nameEQ = name + "=";
+        var ca = document.cookie.split(';');
+
+        for(var i=0; i < ca.length; i++)
+        {
+            var c = ca[i];
+            while (c.charAt(0)==' ') c = c.substring(1,c.length);
+            if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length,c.length);
+        }
+    }
+
+    return null;
+};
+
+this.removeCookie = function(name)
+{
+    this.createCookie(name, "", -1);
+};
+
+
+// ************************************************************************************************
+// http://www.mister-pixel.com/#Content__state=is_that_simple
+var fixIE6BackgroundImageCache = function(doc)
+{
+    doc = doc || document;
+    try
+    {
+        doc.execCommand("BackgroundImageCache", false, true);
+    }
+    catch(E)
+    {
+
+    }
+};
+
+// ************************************************************************************************
+// calculatePixelsPerInch
+
+var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;";
+
+var calculatePixelsPerInch = function calculatePixelsPerInch(doc, body)
+{
+    var inch = FBL.createGlobalElement("div");
+    inch.style.cssText = resetStyle + "width:1in; height:1in; position:absolute; top:-1234px; left:-1234px;";
+    body.appendChild(inch);
+
+    FBL.pixelsPerInch = {
+        x: inch.offsetWidth,
+        y: inch.offsetHeight
+    };
+
+    body.removeChild(inch);
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.SourceLink = function(url, line, type, object, instance)
+{
+    this.href = url;
+    this.instance = instance;
+    this.line = line;
+    this.type = type;
+    this.object = object;
+};
+
+this.SourceLink.prototype =
+{
+    toString: function()
+    {
+        return this.href;
+    },
+    toJSON: function() // until 3.1...
+    {
+        return "{\"href\":\""+this.href+"\", "+
+            (this.line?("\"line\":"+this.line+","):"")+
+            (this.type?(" \"type\":\""+this.type+"\","):"")+
+                    "}";
+    }
+
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+this.SourceText = function(lines, owner)
+{
+    this.lines = lines;
+    this.owner = owner;
+};
+
+this.SourceText.getLineAsHTML = function(lineNo)
+{
+    return escapeForSourceLine(this.lines[lineNo-1]);
+};
+
+
+// ************************************************************************************************
+}).apply(FBL);
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /** @scope s_i18n */ function() { with (FBL) {
+// ************************************************************************************************
+
+// TODO: xxxpedro localization
+var oSTR =
+{
+    "NoMembersWarning": "There are no properties to show for this object.",
+
+    "EmptyStyleSheet": "There are no rules in this stylesheet.",
+    "EmptyElementCSS": "This element has no style rules.",
+    "AccessRestricted": "Access to restricted URI denied.",
+
+    "net.label.Parameters": "Parameters",
+    "net.label.Source": "Source",
+    "URLParameters": "Params",
+
+    "EditStyle": "Edit Element Style...",
+    "NewRule": "New Rule...",
+
+    "NewProp": "New Property...",
+    "EditProp": 'Edit "%s"',
+    "DeleteProp": 'Delete "%s"',
+    "DisableProp": 'Disable "%s"'
+};
+
+// ************************************************************************************************
+
+FBL.$STR = function(name)
+{
+    return oSTR.hasOwnProperty(name) ? oSTR[name] : name;
+};
+
+FBL.$STRF = function(name, args)
+{
+    if (!oSTR.hasOwnProperty(name)) return name;
+
+    var format = oSTR[name];
+    var objIndex = 0;
+
+    var parts = parseFormat(format);
+    var trialIndex = objIndex;
+    var objects = args;
+
+    for (var i= 0; i < parts.length; i++)
+    {
+        var part = parts[i];
+        if (part && typeof(part) == "object")
+        {
+            if (++trialIndex > objects.length)  // then too few parameters for format, assume unformatted.
+            {
+                format = "";
+                objIndex = -1;
+                parts.length = 0;
+                break;
+            }
+        }
+
+    }
+
+    var result = [];
+    for (var i = 0; i < parts.length; ++i)
+    {
+        var part = parts[i];
+        if (part && typeof(part) == "object")
+        {
+            result.push(""+args.shift());
+        }
+        else
+            result.push(part);
+    }
+
+    return result.join("");
+};
+
+// ************************************************************************************************
+
+var parseFormat = function parseFormat(format)
+{
+    var parts = [];
+    if (format.length <= 0)
+        return parts;
+
+    var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/;
+    for (var m = reg.exec(format); m; m = reg.exec(format))
+    {
+        if (m[0].substr(0, 2) == "%%")
+        {
+            parts.push(format.substr(0, m.index));
+            parts.push(m[0].substr(1));
+        }
+        else
+        {
+            var type = m[8] ? m[8] : m[5];
+            var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);
+
+            var rep = null;
+            switch (type)
+            {
+                case "s":
+                    rep = FirebugReps.Text;
+                    break;
+                case "f":
+                case "i":
+                case "d":
+                    rep = FirebugReps.Number;
+                    break;
+                case "o":
+                    rep = null;
+                    break;
+            }
+
+            parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
+            parts.push({rep: rep, precision: precision, type: ("%" + type)});
+        }
+
+        format = format.substr(m.index+m[0].length);
+    }
+
+    parts.push(format);
+    return parts;
+};
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /** @scope s_firebug */ function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Globals
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Internals
+
+var modules = [];
+var panelTypes = [];
+var panelTypeMap = {};
+var reps = [];
+
+var parentPanelMap = {};
+
+
+// ************************************************************************************************
+// Firebug
+
+/**
+ * @namespace describe Firebug
+ * @exports FBL.Firebug as Firebug
+ */
+FBL.Firebug =
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    version:  "Firebug Lite 1.4.0",
+    revision: "$Revision$",
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    modules: modules,
+    panelTypes: panelTypes,
+    panelTypeMap: panelTypeMap,
+    reps: reps,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Initialization
+
+    initialize: function()
+    {
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.initialize", "initializing application");
+
+        Firebug.browser = new Context(Env.browser);
+        Firebug.context = Firebug.browser;
+
+        Firebug.loadPrefs();
+        Firebug.context.persistedState.isOpen = false;
+
+        // Document must be cached before chrome initialization
+        cacheDocument();
+
+        if (Firebug.Inspector && Firebug.Inspector.create)
+            Firebug.Inspector.create();
+
+        if (FBL.CssAnalyzer && FBL.CssAnalyzer.processAllStyleSheets)
+            FBL.CssAnalyzer.processAllStyleSheets(Firebug.browser.document);
+
+        FirebugChrome.initialize();
+
+        dispatch(modules, "initialize", []);
+
+        if (Firebug.disableResourceFetching)
+            Firebug.Console.logFormatted(["Some Firebug Lite features are not working because " +
+            		"resource fetching is disabled. To enabled it set the Firebug Lite option " +
+            		"\"disableResourceFetching\" to \"false\". More info at " +
+            		"http://getfirebug.com/firebuglite#Options"],
+            		Firebug.context, "warn");
+
+        if (Env.onLoad)
+        {
+            var onLoad = Env.onLoad;
+            delete Env.onLoad;
+
+            setTimeout(onLoad, 200);
+        }
+    },
+
+    shutdown: function()
+    {
+        if (Firebug.saveCookies)
+            Firebug.savePrefs();
+
+        if (Firebug.Inspector)
+            Firebug.Inspector.destroy();
+
+        dispatch(modules, "shutdown", []);
+
+        var chromeMap = FirebugChrome.chromeMap;
+
+        for (var name in chromeMap)
+        {
+            if (chromeMap.hasOwnProperty(name))
+            {
+                try
+                {
+                    chromeMap[name].destroy();
+                }
+                catch(E)
+                {
+                    if (FBTrace.DBG_ERRORS) FBTrace.sysout("chrome.destroy() failed to: " + name);
+                }
+            }
+        }
+
+        Firebug.Lite.Cache.Element.clear();
+        Firebug.Lite.Cache.StyleSheet.clear();
+
+        Firebug.browser = null;
+        Firebug.context = null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Registration
+
+    registerModule: function()
+    {
+        modules.push.apply(modules, arguments);
+
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule");
+    },
+
+    registerPanel: function()
+    {
+        panelTypes.push.apply(panelTypes, arguments);
+
+        for (var i = 0, panelType; panelType = arguments[i]; ++i)
+        {
+            panelTypeMap[panelType.prototype.name] = arguments[i];
+
+            if (panelType.prototype.parentPanel)
+                parentPanelMap[panelType.prototype.parentPanel] = 1;
+        }
+
+        if (FBTrace.DBG_INITIALIZE)
+            for (var i = 0; i < arguments.length; ++i)
+                FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name);
+    },
+
+    registerRep: function()
+    {
+        reps.push.apply(reps, arguments);
+    },
+
+    unregisterRep: function()
+    {
+        for (var i = 0; i < arguments.length; ++i)
+            remove(reps, arguments[i]);
+    },
+
+    setDefaultReps: function(funcRep, rep)
+    {
+        FBL.defaultRep = rep;
+        FBL.defaultFuncRep = funcRep;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Reps
+
+    getRep: function(object)
+    {
+        var type = typeof object;
+        if (isIE && isFunction(object))
+            type = "function";
+
+        for (var i = 0; i < reps.length; ++i)
+        {
+            var rep = reps[i];
+            try
+            {
+                if (rep.supportsObject(object, type))
+                {
+                    if (FBTrace.DBG_DOM)
+                        FBTrace.sysout("getRep type: "+type+" object: "+object, rep);
+                    return rep;
+                }
+            }
+            catch (exc)
+            {
+                if (FBTrace.DBG_ERRORS)
+                {
+                    FBTrace.sysout("firebug.getRep FAILS: ", exc.message || exc);
+                    FBTrace.sysout("firebug.getRep reps["+i+"/"+reps.length+"]: Rep="+reps[i].className);
+                    // TODO: xxxpedro add trace to FBTrace logs like in Firebug
+                    //firebug.trace();
+                }
+            }
+        }
+
+        return (type == 'function') ? defaultFuncRep : defaultRep;
+    },
+
+    getRepObject: function(node)
+    {
+        var target = null;
+        for (var child = node; child; child = child.parentNode)
+        {
+            if (hasClass(child, "repTarget"))
+                target = child;
+
+            if (child.repObject)
+            {
+                if (!target && hasClass(child, "repIgnore"))
+                    break;
+                else
+                    return child.repObject;
+            }
+        }
+    },
+
+    getRepNode: function(node)
+    {
+        for (var child = node; child; child = child.parentNode)
+        {
+            if (child.repObject)
+                return child;
+        }
+    },
+
+    getElementByRepObject: function(element, object)
+    {
+        for (var child = element.firstChild; child; child = child.nextSibling)
+        {
+            if (child.repObject == object)
+                return child;
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Preferences
+
+    getPref: function(name)
+    {
+        return Firebug[name];
+    },
+
+    setPref: function(name, value)
+    {
+        Firebug[name] = value;
+
+        Firebug.savePrefs();
+    },
+
+    setPrefs: function(prefs)
+    {
+        for (var name in prefs)
+        {
+            if (prefs.hasOwnProperty(name))
+                Firebug[name] = prefs[name];
+        }
+
+        Firebug.savePrefs();
+    },
+
+    restorePrefs: function()
+    {
+        var Options = Env.DefaultOptions;
+
+        for (var name in Options)
+        {
+            Firebug[name] = Options[name];
+        }
+    },
+
+    loadPrefs: function()
+    {
+        this.restorePrefs();
+
+        var prefs = Store.get("FirebugLite") || {};
+        var options = prefs.options;
+        var persistedState = prefs.persistedState || FBL.defaultPersistedState;
+
+        for (var name in options)
+        {
+            if (options.hasOwnProperty(name))
+                Firebug[name] = options[name];
+        }
+
+        if (Firebug.context && persistedState)
+            Firebug.context.persistedState = persistedState;
+    },
+
+    savePrefs: function()
+    {
+        var prefs = {
+            options: {}
+        };
+
+        var EnvOptions = Env.Options;
+        var options = prefs.options;
+        for (var name in EnvOptions)
+        {
+            if (EnvOptions.hasOwnProperty(name))
+            {
+                options[name] = Firebug[name];
+            }
+        }
+
+        var persistedState = Firebug.context.persistedState;
+        if (!persistedState)
+        {
+            persistedState = Firebug.context.persistedState = FBL.defaultPersistedState;
+        }
+
+        prefs.persistedState = persistedState;
+
+        Store.set("FirebugLite", prefs);
+    },
+
+    erasePrefs: function()
+    {
+        Store.remove("FirebugLite");
+        this.restorePrefs();
+    }
+};
+
+Firebug.restorePrefs();
+
+// xxxpedro should we remove this?
+window.Firebug = FBL.Firebug;
+
+if (!Env.Options.enablePersistent ||
+     Env.Options.enablePersistent && Env.isChromeContext ||
+     Env.isDebugMode)
+        Env.browser.window.Firebug = FBL.Firebug;
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Other methods
+
+FBL.cacheDocument = function cacheDocument()
+{
+    var ElementCache = Firebug.Lite.Cache.Element;
+    var els = Firebug.browser.document.getElementsByTagName("*");
+    for (var i=0, l=els.length, el; i<l; i++)
+    {
+        el = els[i];
+        ElementCache(el);
+    }
+};
+
+// ************************************************************************************************
+
+/**
+ * @class
+ *
+ * Support for listeners registration. This object also extended by Firebug.Module so,
+ * all modules supports listening automatically. Notice that array of listeners
+ * is created for each intance of a module within initialize method. Thus all derived
+ * module classes must ensure that Firebug.Module.initialize method is called for the
+ * super class.
+ */
+Firebug.Listener = function()
+{
+    // The array is created when the first listeners is added.
+    // It can't be created here since derived objects would share
+    // the same array.
+    this.fbListeners = null;
+};
+
+Firebug.Listener.prototype =
+{
+    addListener: function(listener)
+    {
+        if (!this.fbListeners)
+            this.fbListeners = []; // delay the creation until the objects are created so 'this' causes new array for each module
+
+        this.fbListeners.push(listener);
+    },
+
+    removeListener: function(listener)
+    {
+        remove(this.fbListeners, listener);  // if this.fbListeners is null, remove is being called with no add
+    }
+};
+
+// ************************************************************************************************
+
+
+// ************************************************************************************************
+// Module
+
+/**
+ * @module Base class for all modules. Every derived module object must be registered using
+ * <code>Firebug.registerModule</code> method. There is always one instance of a module object
+ * per browser window.
+ * @extends Firebug.Listener
+ */
+Firebug.Module = extend(new Firebug.Listener(),
+/** @extend Firebug.Module */
+{
+    /**
+     * Called when the window is opened.
+     */
+    initialize: function()
+    {
+    },
+
+    /**
+     * Called when the window is closed.
+     */
+    shutdown: function()
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Called when a new context is created but before the page is loaded.
+     */
+    initContext: function(context)
+    {
+    },
+
+    /**
+     * Called after a context is detached to a separate window;
+     */
+    reattachContext: function(browser, context)
+    {
+    },
+
+    /**
+     * Called when a context is destroyed. Module may store info on persistedState for reloaded pages.
+     */
+    destroyContext: function(context, persistedState)
+    {
+    },
+
+    // Called when a FF tab is create or activated (user changes FF tab)
+    // Called after context is created or with context == null (to abort?)
+    showContext: function(browser, context)
+    {
+    },
+
+    /**
+     * Called after a context's page gets DOMContentLoaded
+     */
+    loadedContext: function(context)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    showPanel: function(browser, panel)
+    {
+    },
+
+    showSidePanel: function(browser, panel)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    updateOption: function(name, value)
+    {
+    },
+
+    getObjectByURL: function(context, url)
+    {
+    }
+});
+
+// ************************************************************************************************
+// Panel
+
+/**
+ * @panel Base class for all panels. Every derived panel must define a constructor and
+ * register with "Firebug.registerPanel" method. An instance of the panel
+ * object is created by the framework for each browser tab where Firebug is activated.
+ */
+Firebug.Panel =
+{
+    name: "HelloWorld",
+    title: "Hello World!",
+
+    parentPanel: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    options: {
+        hasCommandLine: false,
+        hasStatusBar: false,
+        hasToolButtons: false,
+
+        // Pre-rendered panels are those included in the skin file (firebug.html)
+        isPreRendered: false,
+        innerHTMLSync: false
+
+        /*
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // To be used by external extensions
+        panelHTML: "",
+        panelCSS: "",
+
+        toolButtonsHTML: ""
+        /**/
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    tabNode: null,
+    panelNode: null,
+    sidePanelNode: null,
+    statusBarNode: null,
+    toolButtonsNode: null,
+
+    panelBarNode: null,
+
+    sidePanelBarBoxNode: null,
+    sidePanelBarNode: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    sidePanelBar: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    searchable: false,
+    editable: true,
+    order: 2147483647,
+    statusSeparator: "<",
+
+    create: function(context, doc)
+    {
+        this.hasSidePanel = parentPanelMap.hasOwnProperty(this.name);
+
+        this.panelBarNode = $("fbPanelBar1");
+        this.sidePanelBarBoxNode = $("fbPanelBar2");
+
+        if (this.hasSidePanel)
+        {
+            this.sidePanelBar = extend({}, PanelBar);
+            this.sidePanelBar.create(this);
+        }
+
+        var options = this.options = extend(Firebug.Panel.options, this.options);
+        var panelId = "fb" + this.name;
+
+        if (options.isPreRendered)
+        {
+            this.panelNode = $(panelId);
+
+            this.tabNode = $(panelId + "Tab");
+            this.tabNode.style.display = "block";
+
+            if (options.hasToolButtons)
+            {
+                this.toolButtonsNode = $(panelId + "Buttons");
+            }
+
+            if (options.hasStatusBar)
+            {
+                this.statusBarBox = $("fbStatusBarBox");
+                this.statusBarNode = $(panelId + "StatusBar");
+            }
+        }
+        else
+        {
+            var containerSufix = this.parentPanel ? "2" : "1";
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            // Create Panel
+            var panelNode = this.panelNode = createElement("div", {
+                id: panelId,
+                className: "fbPanel"
+            });
+
+            $("fbPanel" + containerSufix).appendChild(panelNode);
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            // Create Panel Tab
+            var tabHTML = '<span class="fbTabL"></span><span class="fbTabText">' +
+                    this.title + '</span><span class="fbTabR"></span>';
+
+            var tabNode = this.tabNode = createElement("a", {
+                id: panelId + "Tab",
+                className: "fbTab fbHover",
+                innerHTML: tabHTML
+            });
+
+            if (isIE6)
+            {
+                tabNode.href = "javascript:void(0)";
+            }
+
+            var panelBarNode = this.parentPanel ?
+                    Firebug.chrome.getPanel(this.parentPanel).sidePanelBarNode :
+                    this.panelBarNode;
+
+            panelBarNode.appendChild(tabNode);
+            tabNode.style.display = "block";
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            // create ToolButtons
+            if (options.hasToolButtons)
+            {
+                this.toolButtonsNode = createElement("span", {
+                    id: panelId + "Buttons",
+                    className: "fbToolbarButtons"
+                });
+
+                $("fbToolbarButtons").appendChild(this.toolButtonsNode);
+            }
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            // create StatusBar
+            if (options.hasStatusBar)
+            {
+                this.statusBarBox = $("fbStatusBarBox");
+
+                this.statusBarNode = createElement("span", {
+                    id: panelId + "StatusBar",
+                    className: "fbToolbarButtons fbStatusBar"
+                });
+
+                this.statusBarBox.appendChild(this.statusBarNode);
+            }
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            // create SidePanel
+        }
+
+        this.containerNode = this.panelNode.parentNode;
+
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.create", this.name);
+
+        // xxxpedro contextMenu
+        this.onContextMenu = bind(this.onContextMenu, this);
+
+        /*
+        this.context = context;
+        this.document = doc;
+
+        this.panelNode = doc.createElement("div");
+        this.panelNode.ownerPanel = this;
+
+        setClass(this.panelNode, "panelNode panelNode-"+this.name+" contextUID="+context.uid);
+        doc.body.appendChild(this.panelNode);
+
+        if (FBTrace.DBG_INITIALIZE)
+            FBTrace.sysout("firebug.initialize panelNode for "+this.name+"\n");
+
+        this.initializeNode(this.panelNode);
+        /**/
+    },
+
+    destroy: function(state) // Panel may store info on state
+    {
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.destroy", this.name);
+
+        if (this.hasSidePanel)
+        {
+            this.sidePanelBar.destroy();
+            this.sidePanelBar = null;
+        }
+
+        this.options = null;
+        this.name = null;
+        this.parentPanel = null;
+
+        this.tabNode = null;
+        this.panelNode = null;
+        this.containerNode = null;
+
+        this.toolButtonsNode = null;
+        this.statusBarBox = null;
+        this.statusBarNode = null;
+
+        //if (this.panelNode)
+        //    delete this.panelNode.ownerPanel;
+
+        //this.destroyNode();
+    },
+
+    initialize: function()
+    {
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.initialize", this.name);
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        if (this.hasSidePanel)
+        {
+            this.sidePanelBar.initialize();
+        }
+
+        var options = this.options = extend(Firebug.Panel.options, this.options);
+        var panelId = "fb" + this.name;
+
+        this.panelNode = $(panelId);
+
+        this.tabNode = $(panelId + "Tab");
+        this.tabNode.style.display = "block";
+
+        if (options.hasStatusBar)
+        {
+            this.statusBarBox = $("fbStatusBarBox");
+            this.statusBarNode = $(panelId + "StatusBar");
+        }
+
+        if (options.hasToolButtons)
+        {
+            this.toolButtonsNode = $(panelId + "Buttons");
+        }
+
+        this.containerNode = this.panelNode.parentNode;
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // restore persistent state
+        this.containerNode.scrollTop = this.lastScrollTop;
+
+        // xxxpedro contextMenu
+        addEvent(this.containerNode, "contextmenu", this.onContextMenu);
+
+
+        /// TODO: xxxpedro infoTip Hack
+        Firebug.chrome.currentPanel =
+                Firebug.chrome.selectedPanel && Firebug.chrome.selectedPanel.sidePanelBar ?
+                Firebug.chrome.selectedPanel.sidePanelBar.selectedPanel :
+                Firebug.chrome.selectedPanel;
+
+        Firebug.showInfoTips = true;
+        if (Firebug.InfoTip)
+            Firebug.InfoTip.initializeBrowser(Firebug.chrome);
+    },
+
+    shutdown: function()
+    {
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.Panel.shutdown", this.name);
+
+        /// TODO: xxxpedro infoTip Hack
+        if (Firebug.InfoTip)
+            Firebug.InfoTip.uninitializeBrowser(Firebug.chrome);
+
+        if (Firebug.chrome.largeCommandLineVisible)
+            Firebug.chrome.hideLargeCommandLine();
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        if (this.hasSidePanel)
+        {
+            // TODO: xxxpedro firebug1.3a6
+            // new PanelBar mechanism will need to call shutdown to hide the panels (so it
+            // doesn't appears in other panel's sidePanelBar. Therefore, we need to implement
+            // a "remember selected panel" feature in the sidePanelBar
+            //this.sidePanelBar.shutdown();
+        }
+
+        // store persistent state
+        this.lastScrollTop = this.containerNode.scrollTop;
+
+        // xxxpedro contextMenu
+        removeEvent(this.containerNode, "contextmenu", this.onContextMenu);
+    },
+
+    detach: function(oldChrome, newChrome)
+    {
+        if (oldChrome && oldChrome.selectedPanel && oldChrome.selectedPanel.name == this.name)
+            this.lastScrollTop = oldChrome.selectedPanel.containerNode.scrollTop;
+    },
+
+    reattach: function(doc)
+    {
+        if (this.options.innerHTMLSync)
+            this.synchronizeUI();
+    },
+
+    synchronizeUI: function()
+    {
+        this.containerNode.scrollTop = this.lastScrollTop || 0;
+    },
+
+    show: function(state)
+    {
+        var options = this.options;
+
+        if (options.hasStatusBar)
+        {
+            this.statusBarBox.style.display = "inline";
+            this.statusBarNode.style.display = "inline";
+        }
+
+        if (options.hasToolButtons)
+        {
+            this.toolButtonsNode.style.display = "inline";
+        }
+
+        this.panelNode.style.display = "block";
+
+        this.visible = true;
+
+        if (!this.parentPanel)
+            Firebug.chrome.layout(this);
+    },
+
+    hide: function(state)
+    {
+        var options = this.options;
+
+        if (options.hasStatusBar)
+        {
+            this.statusBarBox.style.display = "none";
+            this.statusBarNode.style.display = "none";
+        }
+
+        if (options.hasToolButtons)
+        {
+            this.toolButtonsNode.style.display = "none";
+        }
+
+        this.panelNode.style.display = "none";
+
+        this.visible = false;
+    },
+
+    watchWindow: function(win)
+    {
+    },
+
+    unwatchWindow: function(win)
+    {
+    },
+
+    updateOption: function(name, value)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Toolbar helpers
+     */
+    showToolbarButtons: function(buttonsId, show)
+    {
+        try
+        {
+            if (!this.context.browser) // XXXjjb this is bug. Somehow the panel context is not FirebugContext.
+            {
+                if (FBTrace.DBG_ERRORS)
+                    FBTrace.sysout("firebug.Panel showToolbarButtons this.context has no browser, this:", this);
+
+                return;
+            }
+            var buttons = this.context.browser.chrome.$(buttonsId);
+            if (buttons)
+                collapse(buttons, show ? "false" : "true");
+        }
+        catch (exc)
+        {
+            if (FBTrace.DBG_ERRORS)
+            {
+                FBTrace.dumpProperties("firebug.Panel showToolbarButtons FAILS", exc);
+                if (!this.context.browser)FBTrace.dumpStack("firebug.Panel showToolbarButtons no browser");
+            }
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    /**
+     * Returns a number indicating the view's ability to inspect the object.
+     *
+     * Zero means not supported, and higher numbers indicate specificity.
+     */
+    supportsObject: function(object)
+    {
+        return 0;
+    },
+
+    hasObject: function(object)  // beyond type testing, is this object selectable?
+    {
+        return false;
+    },
+
+    select: function(object, forceUpdate)
+    {
+        if (!object)
+            object = this.getDefaultSelection(this.context);
+
+        if(FBTrace.DBG_PANELS)
+            FBTrace.sysout("firebug.select "+this.name+" forceUpdate: "+forceUpdate+" "+object+((object==this.selection)?"==":"!=")+this.selection);
+
+        if (forceUpdate || object != this.selection)
+        {
+            this.selection = object;
+            this.updateSelection(object);
+
+            // TODO: xxxpedro
+            // XXXjoe This is kind of cheating, but, feh.
+            //Firebug.chrome.onPanelSelect(object, this);
+            //if (uiListeners.length > 0)
+            //    dispatch(uiListeners, "onPanelSelect", [object, this]);  // TODO: make Firebug.chrome a uiListener
+        }
+    },
+
+    updateSelection: function(object)
+    {
+    },
+
+    markChange: function(skipSelf)
+    {
+        if (this.dependents)
+        {
+            if (skipSelf)
+            {
+                for (var i = 0; i < this.dependents.length; ++i)
+                {
+                    var panelName = this.dependents[i];
+                    if (panelName != this.name)
+                        this.context.invalidatePanels(panelName);
+                }
+            }
+            else
+                this.context.invalidatePanels.apply(this.context, this.dependents);
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    startInspecting: function()
+    {
+    },
+
+    stopInspecting: function(object, cancelled)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    search: function(text, reverse)
+    {
+    },
+
+    /**
+     * Retrieves the search options that this modules supports.
+     * This is used by the search UI to present the proper options.
+     */
+    getSearchOptionsMenuItems: function()
+    {
+        return [
+            Firebug.Search.searchOptionMenu("search.Case Sensitive", "searchCaseSensitive")
+        ];
+    },
+
+    /**
+     * Navigates to the next document whose match parameter returns true.
+     */
+    navigateToNextDocument: function(match, reverse)
+    {
+        // This is an approximation of the UI that is displayed by the location
+        // selector. This should be close enough, although it may be better
+        // to simply generate the sorted list within the module, rather than
+        // sorting within the UI.
+        var self = this;
+        function compare(a, b) {
+            var locA = self.getObjectDescription(a);
+            var locB = self.getObjectDescription(b);
+            if(locA.path > locB.path)
+                return 1;
+            if(locA.path < locB.path)
+                return -1;
+            if(locA.name > locB.name)
+                return 1;
+            if(locA.name < locB.name)
+                return -1;
+            return 0;
+        }
+        var allLocs = this.getLocationList().sort(compare);
+        for (var curPos = 0; curPos < allLocs.length && allLocs[curPos] != this.location; curPos++);
+
+        function transformIndex(index) {
+            if (reverse) {
+                // For the reverse case we need to implement wrap around.
+                var intermediate = curPos - index - 1;
+                return (intermediate < 0 ? allLocs.length : 0) + intermediate;
+            } else {
+                return (curPos + index + 1) % allLocs.length;
+            }
+        };
+
+        for (var next = 0; next < allLocs.length - 1; next++)
+        {
+            var object = allLocs[transformIndex(next)];
+
+            if (match(object))
+            {
+                this.navigate(object);
+                return object;
+            }
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    // Called when "Options" clicked. Return array of
+    // {label: 'name', nol10n: true,  type: "checkbox", checked: <value>, command:function to set <value>}
+    getOptionsMenuItems: function()
+    {
+        return null;
+    },
+
+    /*
+     * Called by chrome.onContextMenu to build the context menu when this panel has focus.
+     * See also FirebugRep for a similar function also called by onContextMenu
+     * Extensions may monkey patch and chain off this call
+     * @param object: the 'realObject', a model value, eg a DOM property
+     * @param target: the HTML element clicked on.
+     * @return an array of menu items.
+     */
+    getContextMenuItems: function(object, target)
+    {
+        return [];
+    },
+
+    getBreakOnMenuItems: function()
+    {
+        return [];
+    },
+
+    getEditor: function(target, value)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getDefaultSelection: function()
+    {
+        return null;
+    },
+
+    browseObject: function(object)
+    {
+    },
+
+    getPopupObject: function(target)
+    {
+        return Firebug.getRepObject(target);
+    },
+
+    getTooltipObject: function(target)
+    {
+        return Firebug.getRepObject(target);
+    },
+
+    showInfoTip: function(infoTip, x, y)
+    {
+
+    },
+
+    getObjectPath: function(object)
+    {
+        return null;
+    },
+
+    // An array of objects that can be passed to getObjectLocation.
+    // The list of things a panel can show, eg sourceFiles.
+    // Only shown if panel.location defined and supportsObject true
+    getLocationList: function()
+    {
+        return null;
+    },
+
+    getDefaultLocation: function()
+    {
+        return null;
+    },
+
+    getObjectLocation: function(object)
+    {
+        return "";
+    },
+
+    // Text for the location list menu eg script panel source file list
+    // return.path: group/category label, return.name: item label
+    getObjectDescription: function(object)
+    {
+        var url = this.getObjectLocation(object);
+        return FBL.splitURLBase(url);
+    },
+
+    /*
+     *  UI signal that a tab needs attention, eg Script panel is currently stopped on a breakpoint
+     *  @param: show boolean, true turns on.
+     */
+    highlight: function(show)
+    {
+        var tab = this.getTab();
+        if (!tab)
+            return;
+
+        if (show)
+            tab.setAttribute("highlight", "true");
+        else
+            tab.removeAttribute("highlight");
+    },
+
+    getTab: function()
+    {
+        var chrome = Firebug.chrome;
+
+        var tab = chrome.$("fbPanelBar2").getTab(this.name);
+        if (!tab)
+            tab = chrome.$("fbPanelBar1").getTab(this.name);
+        return tab;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Support for Break On Next
+
+    /**
+     * Called by the framework when the user clicks on the Break On Next button.
+     * @param {Boolean} armed Set to true if the Break On Next feature is
+     * to be armed for action and set to false if the Break On Next should be disarmed.
+     * If 'armed' is true, then the next call to shouldBreakOnNext should be |true|.
+     */
+    breakOnNext: function(armed)
+    {
+    },
+
+    /**
+     * Called when a panel is selected/displayed. The method should return true
+     * if the Break On Next feature is currently armed for this panel.
+     */
+    shouldBreakOnNext: function()
+    {
+        return false;
+    },
+
+    /**
+     * Returns labels for Break On Next tooltip (one for enabled and one for disabled state).
+     * @param {Boolean} enabled Set to true if the Break On Next feature is
+     * currently activated for this panel.
+     */
+    getBreakOnNextTooltip: function(enabled)
+    {
+        return null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    // xxxpedro contextMenu
+    onContextMenu: function(event)
+    {
+        if (!this.getContextMenuItems)
+            return;
+
+        cancelEvent(event, true);
+
+        var target = event.target || event.srcElement;
+
+        var menu = this.getContextMenuItems(this.selection, target);
+        if (!menu)
+            return;
+
+        var contextMenu = new Menu(
+        {
+            id: "fbPanelContextMenu",
+
+            items: menu
+        });
+
+        contextMenu.show(event.clientX, event.clientY);
+
+        return true;
+
+        /*
+        // TODO: xxxpedro move code to somewhere. code to get cross-browser
+        // window to screen coordinates
+        var box = Firebug.browser.getElementPosition(Firebug.chrome.node);
+
+        var screenY = 0;
+
+        // Firefox
+        if (typeof window.mozInnerScreenY != "undefined")
+        {
+            screenY = window.mozInnerScreenY;
+        }
+        // Chrome
+        else if (typeof window.innerHeight != "undefined")
+        {
+            screenY = window.outerHeight - window.innerHeight;
+        }
+        // IE
+        else if (typeof window.screenTop != "undefined")
+        {
+            screenY = window.screenTop;
+        }
+
+        contextMenu.show(event.screenX-box.left, event.screenY-screenY-box.top);
+        /**/
+    }
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+};
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+/**
+ * MeasureBox
+ * To get pixels size.width and size.height:
+ * <ul><li>     this.startMeasuring(view); </li>
+ *     <li>     var size = this.measureText(lineNoCharsSpacer); </li>
+ *     <li>     this.stopMeasuring(); </li>
+ * </ul>
+ *
+ * @namespace
+ */
+Firebug.MeasureBox =
+{
+    startMeasuring: function(target)
+    {
+        if (!this.measureBox)
+        {
+            this.measureBox = target.ownerDocument.createElement("span");
+            this.measureBox.className = "measureBox";
+        }
+
+        copyTextStyles(target, this.measureBox);
+        target.ownerDocument.body.appendChild(this.measureBox);
+    },
+
+    getMeasuringElement: function()
+    {
+        return this.measureBox;
+    },
+
+    measureText: function(value)
+    {
+        this.measureBox.innerHTML = value ? escapeForSourceLine(value) : "m";
+        return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1};
+    },
+
+    measureInputText: function(value)
+    {
+        value = value ? escapeForTextNode(value) : "m";
+        if (!Firebug.showTextNodesWithWhitespace)
+            value = value.replace(/\t/g,'mmmmmm').replace(/\ /g,'m');
+        this.measureBox.innerHTML = value;
+        return {width: this.measureBox.offsetWidth, height: this.measureBox.offsetHeight-1};
+    },
+
+    getBox: function(target)
+    {
+        var style = this.measureBox.ownerDocument.defaultView.getComputedStyle(this.measureBox, "");
+        var box = getBoxFromStyles(style, this.measureBox);
+        return box;
+    },
+
+    stopMeasuring: function()
+    {
+        this.measureBox.parentNode.removeChild(this.measureBox);
+    }
+};
+
+
+// ************************************************************************************************
+if (FBL.domplate) Firebug.Rep = domplate(
+{
+    className: "",
+    inspectable: true,
+
+    supportsObject: function(object, type)
+    {
+        return false;
+    },
+
+    inspectObject: function(object, context)
+    {
+        Firebug.chrome.select(object);
+    },
+
+    browseObject: function(object, context)
+    {
+    },
+
+    persistObject: function(object, context)
+    {
+    },
+
+    getRealObject: function(object, context)
+    {
+        return object;
+    },
+
+    getTitle: function(object)
+    {
+        var label = safeToString(object);
+
+        var re = /\[object (.*?)\]/;
+        var m = re.exec(label);
+
+        ///return m ? m[1] : label;
+
+        // if the label is in the "[object TYPE]" format return its type
+        if (m)
+        {
+            return m[1];
+        }
+        // if it is IE we need to handle some special cases
+        else if (
+                // safeToString() fails to recognize some objects in IE
+                isIE &&
+                // safeToString() returns "[object]" for some objects like window.Image
+                (label == "[object]" ||
+                // safeToString() returns undefined for some objects like window.clientInformation
+                typeof object == "object" && typeof label == "undefined")
+            )
+        {
+            return "Object";
+        }
+        else
+        {
+            return label;
+        }
+    },
+
+    getTooltip: function(object)
+    {
+        return null;
+    },
+
+    getContextMenuItems: function(object, target, context)
+    {
+        return [];
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Convenience for domplates
+
+    STR: function(name)
+    {
+        return $STR(name);
+    },
+
+    cropString: function(text)
+    {
+        return cropString(text);
+    },
+
+    cropMultipleLines: function(text, limit)
+    {
+        return cropMultipleLines(text, limit);
+    },
+
+    toLowerCase: function(text)
+    {
+        return text ? text.toLowerCase() : text;
+    },
+
+    plural: function(n)
+    {
+        return n == 1 ? "" : "s";
+    }
+});
+
+// ************************************************************************************************
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /** @scope s_gui */ function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Controller
+
+/**@namespace*/
+FBL.Controller = {
+
+    controllers: null,
+    controllerContext: null,
+
+    initialize: function(context)
+    {
+        this.controllers = [];
+        this.controllerContext = context || Firebug.chrome;
+    },
+
+    shutdown: function()
+    {
+        this.removeControllers();
+
+        //this.controllers = null;
+        //this.controllerContext = null;
+    },
+
+    addController: function()
+    {
+        for (var i=0, arg; arg=arguments[i]; i++)
+        {
+            // If the first argument is a string, make a selector query
+            // within the controller node context
+            if (typeof arg[0] == "string")
+            {
+                arg[0] = $$(arg[0], this.controllerContext);
+            }
+
+            // bind the handler to the proper context
+            var handler = arg[2];
+            arg[2] = bind(handler, this);
+            // save the original handler as an extra-argument, so we can
+            // look for it later, when removing a particular controller
+            arg[3] = handler;
+
+            this.controllers.push(arg);
+            addEvent.apply(this, arg);
+        }
+    },
+
+    removeController: function()
+    {
+        for (var i=0, arg; arg=arguments[i]; i++)
+        {
+            for (var j=0, c; c=this.controllers[j]; j++)
+            {
+                if (arg[0] == c[0] && arg[1] == c[1] && arg[2] == c[3])
+                    removeEvent.apply(this, c);
+            }
+        }
+    },
+
+    removeControllers: function()
+    {
+        for (var i=0, c; c=this.controllers[i]; i++)
+        {
+            removeEvent.apply(this, c);
+        }
+    }
+};
+
+
+// ************************************************************************************************
+// PanelBar
+
+/**@namespace*/
+FBL.PanelBar =
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    panelMap: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    selectedPanel: null,
+    parentPanelName: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    create: function(ownerPanel)
+    {
+        this.panelMap = {};
+        this.ownerPanel = ownerPanel;
+
+        if (ownerPanel)
+        {
+            ownerPanel.sidePanelBarNode = createElement("span");
+            ownerPanel.sidePanelBarNode.style.display = "none";
+            ownerPanel.sidePanelBarBoxNode.appendChild(ownerPanel.sidePanelBarNode);
+        }
+
+        var panels = Firebug.panelTypes;
+        for (var i=0, p; p=panels[i]; i++)
+        {
+            if ( // normal Panel  of the Chrome's PanelBar
+                !ownerPanel && !p.prototype.parentPanel ||
+                // Child Panel of the current Panel's SidePanelBar
+                ownerPanel && p.prototype.parentPanel &&
+                ownerPanel.name == p.prototype.parentPanel)
+            {
+                this.addPanel(p.prototype.name);
+            }
+        }
+    },
+
+    destroy: function()
+    {
+        PanelBar.shutdown.call(this);
+
+        for (var name in this.panelMap)
+        {
+            this.removePanel(name);
+
+            var panel = this.panelMap[name];
+            panel.destroy();
+
+            this.panelMap[name] = null;
+            delete this.panelMap[name];
+        }
+
+        this.panelMap = null;
+        this.ownerPanel = null;
+    },
+
+    initialize: function()
+    {
+        if (this.ownerPanel)
+            this.ownerPanel.sidePanelBarNode.style.display = "inline";
+
+        for(var name in this.panelMap)
+        {
+            (function(self, name){
+
+                // tab click handler
+                var onTabClick = function onTabClick()
+                {
+                    self.selectPanel(name);
+                    return false;
+                };
+
+                Firebug.chrome.addController([self.panelMap[name].tabNode, "mousedown", onTabClick]);
+
+            })(this, name);
+        }
+    },
+
+    shutdown: function()
+    {
+        var selectedPanel = this.selectedPanel;
+
+        if (selectedPanel)
+        {
+            removeClass(selectedPanel.tabNode, "fbSelectedTab");
+            selectedPanel.hide();
+            selectedPanel.shutdown();
+        }
+
+        if (this.ownerPanel)
+            this.ownerPanel.sidePanelBarNode.style.display = "none";
+
+        this.selectedPanel = null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    addPanel: function(panelName, parentPanel)
+    {
+        var PanelType = Firebug.panelTypeMap[panelName];
+        var panel = this.panelMap[panelName] = new PanelType();
+
+        panel.create();
+    },
+
+    removePanel: function(panelName)
+    {
+        var panel = this.panelMap[panelName];
+        if (panel.hasOwnProperty(panelName))
+            panel.destroy();
+    },
+
+    selectPanel: function(panelName)
+    {
+        var selectedPanel = this.selectedPanel;
+        var panel = this.panelMap[panelName];
+
+        if (panel && selectedPanel != panel)
+        {
+            if (selectedPanel)
+            {
+                removeClass(selectedPanel.tabNode, "fbSelectedTab");
+                selectedPanel.shutdown();
+                selectedPanel.hide();
+            }
+
+            if (!panel.parentPanel)
+                Firebug.context.persistedState.selectedPanelName = panelName;
+
+            this.selectedPanel = panel;
+
+            setClass(panel.tabNode, "fbSelectedTab");
+            panel.show();
+            panel.initialize();
+        }
+    },
+
+    getPanel: function(panelName)
+    {
+        var panel = this.panelMap[panelName];
+
+        return panel;
+    }
+
+};
+
+//************************************************************************************************
+// Button
+
+/**
+ * options.element
+ * options.caption
+ * options.title
+ *
+ * options.owner
+ * options.className
+ * options.pressedClassName
+ *
+ * options.onPress
+ * options.onUnpress
+ * options.onClick
+ *
+ * @class
+ * @extends FBL.Controller
+ *
+ */
+
+FBL.Button = function(options)
+{
+    options = options || {};
+
+    append(this, options);
+
+    this.state = "unpressed";
+    this.display = "unpressed";
+
+    if (this.element)
+    {
+        this.container = this.element.parentNode;
+    }
+    else
+    {
+        this.shouldDestroy = true;
+
+        this.container = this.owner.getPanel().toolButtonsNode;
+
+        this.element = createElement("a", {
+            className: this.baseClassName + " " + this.className + " fbHover",
+            innerHTML: this.caption
+        });
+
+        if (this.title)
+            this.element.title = this.title;
+
+        this.container.appendChild(this.element);
+    }
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+Button.prototype = extend(Controller,
+/**@extend FBL.Button.prototype*/
+{
+    type: "normal",
+    caption: "caption",
+    title: null,
+
+    className: "", // custom class
+    baseClassName: "fbButton", // control class
+    pressedClassName: "fbBtnPressed", // control pressed class
+
+    element: null,
+    container: null,
+    owner: null,
+
+    state: null,
+    display: null,
+
+    destroy: function()
+    {
+        this.shutdown();
+
+        // only remove if it is a dynamically generated button (not pre-rendered)
+        if (this.shouldDestroy)
+            this.container.removeChild(this.element);
+
+        this.element = null;
+        this.container = null;
+        this.owner = null;
+    },
+
+    initialize: function()
+    {
+        Controller.initialize.apply(this);
+
+        var element = this.element;
+
+        this.addController([element, "mousedown", this.handlePress]);
+
+        if (this.type == "normal")
+            this.addController(
+                [element, "mouseup", this.handleUnpress],
+                [element, "mouseout", this.handleUnpress],
+                [element, "click", this.handleClick]
+            );
+    },
+
+    shutdown: function()
+    {
+        Controller.shutdown.apply(this);
+    },
+
+    restore: function()
+    {
+        this.changeState("unpressed");
+    },
+
+    changeState: function(state)
+    {
+        this.state = state;
+        this.changeDisplay(state);
+    },
+
+    changeDisplay: function(display)
+    {
+        if (display != this.display)
+        {
+            if (display == "pressed")
+            {
+                setClass(this.element, this.pressedClassName);
+            }
+            else if (display == "unpressed")
+            {
+                removeClass(this.element, this.pressedClassName);
+            }
+            this.display = display;
+        }
+    },
+
+    handlePress: function(event)
+    {
+        cancelEvent(event, true);
+
+        if (this.type == "normal")
+        {
+            this.changeDisplay("pressed");
+            this.beforeClick = true;
+        }
+        else if (this.type == "toggle")
+        {
+            if (this.state == "pressed")
+            {
+                this.changeState("unpressed");
+
+                if (this.onUnpress)
+                    this.onUnpress.apply(this.owner, arguments);
+            }
+            else
+            {
+                this.changeState("pressed");
+
+                if (this.onPress)
+                    this.onPress.apply(this.owner, arguments);
+            }
+
+            if (this.onClick)
+                this.onClick.apply(this.owner, arguments);
+        }
+
+        return false;
+    },
+
+    handleUnpress: function(event)
+    {
+        cancelEvent(event, true);
+
+        if (this.beforeClick)
+            this.changeDisplay("unpressed");
+
+        return false;
+    },
+
+    handleClick: function(event)
+    {
+        cancelEvent(event, true);
+
+        if (this.type == "normal")
+        {
+            if (this.onClick)
+                this.onClick.apply(this.owner);
+
+            this.changeState("unpressed");
+        }
+
+        this.beforeClick = false;
+
+        return false;
+    }
+});
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+/**
+ * @class
+ * @extends FBL.Button
+ */
+FBL.IconButton = function()
+{
+    Button.apply(this, arguments);
+};
+
+IconButton.prototype = extend(Button.prototype,
+/**@extend FBL.IconButton.prototype*/
+{
+    baseClassName: "fbIconButton",
+    pressedClassName: "fbIconPressed"
+});
+
+
+//************************************************************************************************
+// Menu
+
+var menuItemProps = {"class": "$item.className", type: "$item.type", value: "$item.value",
+        _command: "$item.command"};
+
+if (isIE6)
+    menuItemProps.href = "javascript:void(0)";
+
+// Allow GUI to be loaded even when Domplate module is not installed.
+if (FBL.domplate)
+var MenuPlate = domplate(Firebug.Rep,
+{
+    tag:
+        DIV({"class": "fbMenu fbShadow"},
+            DIV({"class": "fbMenuContent fbShadowContent"},
+                FOR("item", "$object.items|memberIterator",
+                    TAG("$item.tag", {item: "$item"})
+                )
+            )
+        ),
+
+    itemTag:
+        A(menuItemProps,
+            "$item.label"
+        ),
+
+    checkBoxTag:
+        A(extend(menuItemProps, {checked : "$item.checked"}),
+
+            "$item.label"
+        ),
+
+    radioButtonTag:
+        A(extend(menuItemProps, {selected : "$item.selected"}),
+
+            "$item.label"
+        ),
+
+    groupTag:
+        A(extend(menuItemProps, {child: "$item.child"}),
+            "$item.label"
+        ),
+
+    shortcutTag:
+        A(menuItemProps,
+            "$item.label",
+            SPAN({"class": "fbMenuShortcutKey"},
+                "$item.key"
+            )
+        ),
+
+    separatorTag:
+        SPAN({"class": "fbMenuSeparator"}),
+
+    memberIterator: function(items)
+    {
+        var result = [];
+
+        for (var i=0, length=items.length; i<length; i++)
+        {
+            var item = items[i];
+
+            // separator representation
+            if (typeof item == "string" && item.indexOf("-") == 0)
+            {
+                result.push({tag: this.separatorTag});
+                continue;
+            }
+
+            item = extend(item, {});
+
+            item.type = item.type || "";
+            item.value = item.value || "";
+
+            var type = item.type;
+
+            // default item representation
+            item.tag = this.itemTag;
+
+            var className = item.className || "";
+
+            className += "fbMenuOption fbHover ";
+
+            // specific representations
+            if (type == "checkbox")
+            {
+                className += "fbMenuCheckBox ";
+                item.tag = this.checkBoxTag;
+            }
+            else if (type == "radiobutton")
+            {
+                className += "fbMenuRadioButton ";
+                item.tag = this.radioButtonTag;
+            }
+            else if (type == "group")
+            {
+                className += "fbMenuGroup ";
+                item.tag = this.groupTag;
+            }
+            else if (type == "shortcut")
+            {
+                className += "fbMenuShortcut ";
+                item.tag = this.shortcutTag;
+            }
+
+            if (item.checked)
+                className += "fbMenuChecked ";
+            else if (item.selected)
+                className += "fbMenuRadioSelected ";
+
+            if (item.disabled)
+                className += "fbMenuDisabled ";
+
+            item.className = className;
+
+            item.label = $STR(item.label);
+
+            result.push(item);
+        }
+
+        return result;
+    }
+});
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+/**
+ * options
+ * options.element
+ * options.id
+ * options.items
+ *
+ * item.label
+ * item.className
+ * item.type
+ * item.value
+ * item.disabled
+ * item.checked
+ * item.selected
+ * item.command
+ * item.child
+ *
+ *
+ * @class
+ * @extends FBL.Controller
+ *
+ */
+FBL.Menu = function(options)
+{
+    // if element is not pre-rendered, we must render it now
+    if (!options.element)
+    {
+        if (options.getItems)
+            options.items = options.getItems();
+
+        options.element = MenuPlate.tag.append(
+                {object: options},
+                getElementByClass(Firebug.chrome.document, "fbBody"),
+                MenuPlate
+            );
+    }
+
+    // extend itself with the provided options
+    append(this, options);
+
+    if (typeof this.element == "string")
+    {
+        this.id = this.element;
+        this.element = $(this.id);
+    }
+    else if (this.id)
+    {
+        this.element.id = this.id;
+    }
+
+    this.element.firebugIgnore = true;
+    this.elementStyle = this.element.style;
+
+    this.isVisible = false;
+
+    this.handleMouseDown = bind(this.handleMouseDown, this);
+    this.handleMouseOver = bind(this.handleMouseOver, this);
+    this.handleMouseOut = bind(this.handleMouseOut, this);
+
+    this.handleWindowMouseDown = bind(this.handleWindowMouseDown, this);
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var menuMap = {};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+Menu.prototype =  extend(Controller,
+/**@extend FBL.Menu.prototype*/
+{
+    destroy: function()
+    {
+        //if (this.element) console.log("destroy", this.element.id);
+
+        this.hide();
+
+        // if it is a childMenu, remove its reference from the parentMenu
+        if (this.parentMenu)
+            this.parentMenu.childMenu = null;
+
+        // remove the element from the document
+        this.element.parentNode.removeChild(this.element);
+
+        // clear references
+        this.element = null;
+        this.elementStyle = null;
+        this.parentMenu = null;
+        this.parentTarget = null;
+    },
+
+    initialize: function()
+    {
+        Controller.initialize.call(this);
+
+        this.addController(
+                [this.element, "mousedown", this.handleMouseDown],
+                [this.element, "mouseover", this.handleMouseOver]
+             );
+    },
+
+    shutdown: function()
+    {
+        Controller.shutdown.call(this);
+    },
+
+    show: function(x, y)
+    {
+        this.initialize();
+
+        if (this.isVisible) return;
+
+        //console.log("show", this.element.id);
+
+        x = x || 0;
+        y = y || 0;
+
+        if (this.parentMenu)
+        {
+            var oldChildMenu = this.parentMenu.childMenu;
+            if (oldChildMenu && oldChildMenu != this)
+            {
+                oldChildMenu.destroy();
+            }
+
+            this.parentMenu.childMenu = this;
+        }
+        else
+            addEvent(Firebug.chrome.document, "mousedown", this.handleWindowMouseDown);
+
+        this.elementStyle.display = "block";
+        this.elementStyle.visibility = "hidden";
+
+        var size = Firebug.chrome.getSize();
+
+        x = Math.min(x, size.width - this.element.clientWidth - 10);
+        x = Math.max(x, 0);
+
+        y = Math.min(y, size.height - this.element.clientHeight - 10);
+        y = Math.max(y, 0);
+
+        this.elementStyle.left = x + "px";
+        this.elementStyle.top = y + "px";
+
+        this.elementStyle.visibility = "visible";
+
+        this.isVisible = true;
+
+        if (isFunction(this.onShow))
+            this.onShow.apply(this, arguments);
+    },
+
+    hide: function()
+    {
+        this.clearHideTimeout();
+        this.clearShowChildTimeout();
+
+        if (!this.isVisible) return;
+
+        //console.log("hide", this.element.id);
+
+        this.elementStyle.display = "none";
+
+        if(this.childMenu)
+        {
+            this.childMenu.destroy();
+            this.childMenu = null;
+        }
+
+        if(this.parentTarget)
+            removeClass(this.parentTarget, "fbMenuGroupSelected");
+
+        this.isVisible = false;
+
+        this.shutdown();
+
+        if (isFunction(this.onHide))
+            this.onHide.apply(this, arguments);
+    },
+
+    showChildMenu: function(target)
+    {
+        var id = target.getAttribute("child");
+
+        var parent = this;
+        var target = target;
+
+        this.showChildTimeout = Firebug.chrome.window.setTimeout(function(){
+
+            //if (!parent.isVisible) return;
+
+            var box = Firebug.chrome.getElementBox(target);
+
+            var childMenuObject = menuMap.hasOwnProperty(id) ?
+                    menuMap[id] : {element: $(id)};
+
+            var childMenu = new Menu(extend(childMenuObject,
+                {
+                    parentMenu: parent,
+                    parentTarget: target
+                }));
+
+            var offsetLeft = isIE6 ? -1 : -6; // IE6 problem with fixed position
+            childMenu.show(box.left + box.width + offsetLeft, box.top -6);
+            setClass(target, "fbMenuGroupSelected");
+
+        },350);
+    },
+
+    clearHideTimeout: function()
+    {
+        if (this.hideTimeout)
+        {
+            Firebug.chrome.window.clearTimeout(this.hideTimeout);
+            delete this.hideTimeout;
+        }
+    },
+
+    clearShowChildTimeout: function()
+    {
+        if(this.showChildTimeout)
+        {
+            Firebug.chrome.window.clearTimeout(this.showChildTimeout);
+            this.showChildTimeout = null;
+        }
+    },
+
+    handleMouseDown: function(event)
+    {
+        cancelEvent(event, true);
+
+        var topParent = this;
+        while (topParent.parentMenu)
+            topParent = topParent.parentMenu;
+
+        var target = event.target || event.srcElement;
+
+        target = getAncestorByClass(target, "fbMenuOption");
+
+        if(!target || hasClass(target, "fbMenuGroup"))
+            return false;
+
+        if (target && !hasClass(target, "fbMenuDisabled"))
+        {
+            var type = target.getAttribute("type");
+
+            if (type == "checkbox")
+            {
+                var checked = target.getAttribute("checked");
+                var value = target.getAttribute("value");
+                var wasChecked = hasClass(target, "fbMenuChecked");
+
+                if (wasChecked)
+                {
+                    removeClass(target, "fbMenuChecked");
+                    target.setAttribute("checked", "");
+                }
+                else
+                {
+                    setClass(target, "fbMenuChecked");
+                    target.setAttribute("checked", "true");
+                }
+
+                if (isFunction(this.onCheck))
+                    this.onCheck.call(this, target, value, !wasChecked);
+            }
+
+            if (type == "radiobutton")
+            {
+                var selectedRadios = getElementsByClass(target.parentNode, "fbMenuRadioSelected");
+
+                var group = target.getAttribute("group");
+
+                for (var i = 0, length = selectedRadios.length; i < length; i++)
+                {
+                    radio = selectedRadios[i];
+
+                    if (radio.getAttribute("group") == group)
+                    {
+                        removeClass(radio, "fbMenuRadioSelected");
+                        radio.setAttribute("selected", "");
+                    }
+                }
+
+                setClass(target, "fbMenuRadioSelected");
+                target.setAttribute("selected", "true");
+            }
+
+            var handler = null;
+
+            // target.command can be a function or a string.
+            var cmd = target.command;
+
+            // If it is a function it will be used as the handler
+            if (isFunction(cmd))
+                handler = cmd;
+            // If it is a string it the property of the current menu object
+            // will be used as the handler
+            else if (typeof cmd == "string")
+                handler = this[cmd];
+
+            var closeMenu = true;
+
+            if (handler)
+                closeMenu = handler.call(this, target) !== false;
+
+            if (closeMenu)
+                topParent.hide();
+        }
+
+        return false;
+    },
+
+    handleWindowMouseDown: function(event)
+    {
+        //console.log("handleWindowMouseDown");
+
+        var target = event.target || event.srcElement;
+
+        target = getAncestorByClass(target, "fbMenu");
+
+        if (!target)
+        {
+            removeEvent(Firebug.chrome.document, "mousedown", this.handleWindowMouseDown);
+            this.hide();
+        }
+    },
+
+    handleMouseOver: function(event)
+    {
+        //console.log("handleMouseOver", this.element.id);
+
+        this.clearHideTimeout();
+        this.clearShowChildTimeout();
+
+        var target = event.target || event.srcElement;
+
+        target = getAncestorByClass(target, "fbMenuOption");
+
+        if(!target)
+            return;
+
+        var childMenu = this.childMenu;
+        if(childMenu)
+        {
+            removeClass(childMenu.parentTarget, "fbMenuGroupSelected");
+
+            if (childMenu.parentTarget != target && childMenu.isVisible)
+            {
+                childMenu.clearHideTimeout();
+                childMenu.hideTimeout = Firebug.chrome.window.setTimeout(function(){
+                    childMenu.destroy();
+                },300);
+            }
+        }
+
+        if(hasClass(target, "fbMenuGroup"))
+        {
+            this.showChildMenu(target);
+        }
+    }
+});
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+append(Menu,
+/**@extend FBL.Menu*/
+{
+    register: function(object)
+    {
+        menuMap[object.id] = object;
+    },
+
+    check: function(element)
+    {
+        setClass(element, "fbMenuChecked");
+        element.setAttribute("checked", "true");
+    },
+
+    uncheck: function(element)
+    {
+        removeClass(element, "fbMenuChecked");
+        element.setAttribute("checked", "");
+    },
+
+    disable: function(element)
+    {
+        setClass(element, "fbMenuDisabled");
+    },
+
+    enable: function(element)
+    {
+        removeClass(element, "fbMenuDisabled");
+    }
+});
+
+
+//************************************************************************************************
+// Status Bar
+
+/**@class*/
+function StatusBar(){};
+
+StatusBar.prototype = extend(Controller, {
+
+});
+
+// ************************************************************************************************
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /**@scope s_context*/ function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Globals
+
+var refreshDelay = 300;
+
+// Opera and some versions of webkit returns the wrong value of document.elementFromPoint()
+// function, without taking into account the scroll position. Safari 4 (webkit/531.21.8)
+// still have this issue. Google Chrome 4 (webkit/532.5) does not. So, we're assuming this
+// issue was fixed in the 532 version
+var shouldFixElementFromPoint = isOpera || isSafari && browserVersion < "532";
+
+var evalError = "___firebug_evaluation_error___";
+var pixelsPerInch;
+
+var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;";
+var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;";
+
+
+// ************************************************************************************************
+// Context
+
+/** @class */
+FBL.Context = function(win)
+{
+    this.window = win.window;
+    this.document = win.document;
+
+    this.browser = Env.browser;
+
+    // Some windows in IE, like iframe, doesn't have the eval() method
+    if (isIE && !this.window.eval)
+    {
+        // But after executing the following line the method magically appears!
+        this.window.execScript("null");
+        // Just to make sure the "magic" really happened
+        if (!this.window.eval)
+            throw new Error("Firebug Error: eval() method not found in this window");
+    }
+
+    // Create a new "black-box" eval() method that runs in the global namespace
+    // of the context window, without exposing the local variables declared
+    // by the function that calls it
+    this.eval = this.window.eval("new Function('" +
+            "try{ return window.eval.apply(window,arguments) }catch(E){ E."+evalError+"=true; return E }" +
+        "')");
+};
+
+FBL.Context.prototype =
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // partial-port of Firebug tabContext.js
+
+    browser: null,
+    loaded: true,
+
+    setTimeout: function(fn, delay)
+    {
+        var win = this.window;
+
+        if (win.setTimeout == this.setTimeout)
+            throw new Error("setTimeout recursion");
+
+        var timeout = win.setTimeout.apply ? // IE doesn't have apply method on setTimeout
+                win.setTimeout.apply(win, arguments) :
+                win.setTimeout(fn, delay);
+
+        if (!this.timeouts)
+            this.timeouts = {};
+
+        this.timeouts[timeout] = 1;
+
+        return timeout;
+    },
+
+    clearTimeout: function(timeout)
+    {
+        clearTimeout(timeout);
+
+        if (this.timeouts)
+            delete this.timeouts[timeout];
+    },
+
+    setInterval: function(fn, delay)
+    {
+        var win = this.window;
+
+        var timeout = win.setInterval.apply ? // IE doesn't have apply method on setTimeout
+                win.setInterval.apply(win, arguments) :
+                win.setInterval(fn, delay);
+
+        if (!this.intervals)
+            this.intervals = {};
+
+        this.intervals[timeout] = 1;
+
+        return timeout;
+    },
+
+    clearInterval: function(timeout)
+    {
+        clearInterval(timeout);
+
+        if (this.intervals)
+            delete this.intervals[timeout];
+    },
+
+    invalidatePanels: function()
+    {
+        if (!this.invalidPanels)
+            this.invalidPanels = {};
+
+        for (var i = 0; i < arguments.length; ++i)
+        {
+            var panelName = arguments[i];
+
+            // avoid error. need to create a better getPanel() function as explained below
+            if (!Firebug.chrome || !Firebug.chrome.selectedPanel)
+                return;
+
+            //var panel = this.getPanel(panelName, true);
+            //TODO: xxxpedro context how to get all panels using a single function?
+            // the current workaround to make the invalidation works is invalidating
+            // only sidePanels. There's also a problem with panel name (LowerCase in Firebug Lite)
+            var panel = Firebug.chrome.selectedPanel.sidePanelBar ?
+                    Firebug.chrome.selectedPanel.sidePanelBar.getPanel(panelName, true) :
+                    null;
+
+            if (panel && !panel.noRefresh)
+                this.invalidPanels[panelName] = 1;
+        }
+
+        if (this.refreshTimeout)
+        {
+            this.clearTimeout(this.refreshTimeout);
+            delete this.refreshTimeout;
+        }
+
+        this.refreshTimeout = this.setTimeout(bindFixed(function()
+        {
+            var invalids = [];
+
+            for (var panelName in this.invalidPanels)
+            {
+                //var panel = this.getPanel(panelName, true);
+                //TODO: xxxpedro context how to get all panels using a single function?
+                // the current workaround to make the invalidation works is invalidating
+                // only sidePanels. There's also a problem with panel name (LowerCase in Firebug Lite)
+                var panel = Firebug.chrome.selectedPanel.sidePanelBar ?
+                        Firebug.chrome.selectedPanel.sidePanelBar.getPanel(panelName, true) :
+                        null;
+
+                if (panel)
+                {
+                    if (panel.visible && !panel.editing)
+                        panel.refresh();
+                    else
+                        panel.needsRefresh = true;
+
+                    // If the panel is being edited, we'll keep trying to
+                    // refresh it until editing is done
+                    if (panel.editing)
+                        invalids.push(panelName);
+                }
+            }
+
+            delete this.invalidPanels;
+            delete this.refreshTimeout;
+
+            // Keep looping until every tab is valid
+            if (invalids.length)
+                this.invalidatePanels.apply(this, invalids);
+        }, this), refreshDelay);
+    },
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Evalutation Method
+
+    /**
+     * Evaluates an expression in the current context window.
+     *
+     * @param {String}   expr           expression to be evaluated
+     *
+     * @param {String}   context        string indicating the global location
+     *                                  of the object that will be used as the
+     *                                  context. The context is referred in
+     *                                  the expression as the "this" keyword.
+     *                                  If no context is informed, the "window"
+     *                                  context is used.
+     *
+     * @param {String}   api            string indicating the global location
+     *                                  of the object that will be used as the
+     *                                  api of the evaluation.
+     *
+     * @param {Function} errorHandler(message) error handler to be called
+     *                                         if the evaluation fails.
+     */
+    evaluate: function(expr, context, api, errorHandler)
+    {
+        // the default context is the "window" object. It can be any string that represents
+        // a global accessible element as: "my.namespaced.object"
+        context = context || "window";
+
+        var isObjectLiteral = trim(expr).indexOf("{") == 0,
+            cmd,
+            result;
+
+        // if the context is the "window" object, we don't need a closure
+        if (context == "window")
+        {
+            // If it is an object literal, then wrap the expression with parenthesis so we can
+            // capture the return value
+            if (isObjectLiteral)
+            {
+                cmd = api ?
+                    "with("+api+"){ ("+expr+") }" :
+                    "(" + expr + ")";
+            }
+            else
+            {
+                cmd = api ?
+                    "with("+api+"){ "+expr+" }" :
+                    expr;
+            }
+        }
+        else
+        {
+            cmd = api ?
+                // with API and context, no return value
+                "(function(arguments){ with(" + api + "){ " +
+                    expr +
+                " } }).call(" + context + ",undefined)"
+                :
+                // with context only, no return value
+                "(function(arguments){ " +
+                    expr +
+                " }).call(" + context + ",undefined)";
+        }
+
+        result = this.eval(cmd);
+
+        if (result && result[evalError])
+        {
+            var msg = result.name ? (result.name + ": ") : "";
+            msg += result.message || result;
+
+            if (errorHandler)
+                result = errorHandler(msg);
+            else
+                result = msg;
+        }
+
+        return result;
+    },
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Window Methods
+
+    getWindowSize: function()
+    {
+        var width=0, height=0, el;
+
+        if (typeof this.window.innerWidth == "number")
+        {
+            width = this.window.innerWidth;
+            height = this.window.innerHeight;
+        }
+        else if ((el=this.document.documentElement) && (el.clientHeight || el.clientWidth))
+        {
+            width = el.clientWidth;
+            height = el.clientHeight;
+        }
+        else if ((el=this.document.body) && (el.clientHeight || el.clientWidth))
+        {
+            width = el.clientWidth;
+            height = el.clientHeight;
+        }
+
+        return {width: width, height: height};
+    },
+
+    getWindowScrollSize: function()
+    {
+        var width=0, height=0, el;
+
+        // first try the document.documentElement scroll size
+        if (!isIEQuiksMode && (el=this.document.documentElement) &&
+           (el.scrollHeight || el.scrollWidth))
+        {
+            width = el.scrollWidth;
+            height = el.scrollHeight;
+        }
+
+        // then we need to check if document.body has a bigger scroll size value
+        // because sometimes depending on the browser and the page, the document.body
+        // scroll size returns a smaller (and wrong) measure
+        if ((el=this.document.body) && (el.scrollHeight || el.scrollWidth) &&
+            (el.scrollWidth > width || el.scrollHeight > height))
+        {
+            width = el.scrollWidth;
+            height = el.scrollHeight;
+        }
+
+        return {width: width, height: height};
+    },
+
+    getWindowScrollPosition: function()
+    {
+        var top=0, left=0, el;
+
+        if(typeof this.window.pageYOffset == "number")
+        {
+            top = this.window.pageYOffset;
+            left = this.window.pageXOffset;
+        }
+        else if((el=this.document.body) && (el.scrollTop || el.scrollLeft))
+        {
+            top = el.scrollTop;
+            left = el.scrollLeft;
+        }
+        else if((el=this.document.documentElement) && (el.scrollTop || el.scrollLeft))
+        {
+            top = el.scrollTop;
+            left = el.scrollLeft;
+        }
+
+        return {top:top, left:left};
+    },
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Element Methods
+
+    getElementFromPoint: function(x, y)
+    {
+        if (shouldFixElementFromPoint)
+        {
+            var scroll = this.getWindowScrollPosition();
+            return this.document.elementFromPoint(x + scroll.left, y + scroll.top);
+        }
+        else
+            return this.document.elementFromPoint(x, y);
+    },
+
+    getElementPosition: function(el)
+    {
+        var left = 0;
+        var top = 0;
+
+        do
+        {
+            left += el.offsetLeft;
+            top += el.offsetTop;
+        }
+        while (el = el.offsetParent);
+
+        return {left:left, top:top};
+    },
+
+    getElementBox: function(el)
+    {
+        var result = {};
+
+        if (el.getBoundingClientRect)
+        {
+            var rect = el.getBoundingClientRect();
+
+            // fix IE problem with offset when not in fullscreen mode
+            var offset = isIE ? this.document.body.clientTop || this.document.documentElement.clientTop: 0;
+
+            var scroll = this.getWindowScrollPosition();
+
+            result.top = Math.round(rect.top - offset + scroll.top);
+            result.left = Math.round(rect.left - offset + scroll.left);
+            result.height = Math.round(rect.bottom - rect.top);
+            result.width = Math.round(rect.right - rect.left);
+        }
+        else
+        {
+            var position = this.getElementPosition(el);
+
+            result.top = position.top;
+            result.left = position.left;
+            result.height = el.offsetHeight;
+            result.width = el.offsetWidth;
+        }
+
+        return result;
+    },
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Measurement Methods
+
+    getMeasurement: function(el, name)
+    {
+        var result = {value: 0, unit: "px"};
+
+        var cssValue = this.getStyle(el, name);
+
+        if (!cssValue) return result;
+        if (cssValue.toLowerCase() == "auto") return result;
+
+        var reMeasure = /(\d+\.?\d*)(.*)/;
+        var m = cssValue.match(reMeasure);
+
+        if (m)
+        {
+            result.value = m[1]-0;
+            result.unit = m[2].toLowerCase();
+        }
+
+        return result;
+    },
+
+    getMeasurementInPixels: function(el, name)
+    {
+        if (!el) return null;
+
+        var m = this.getMeasurement(el, name);
+        var value = m.value;
+        var unit = m.unit;
+
+        if (unit == "px")
+            return value;
+
+        else if (unit == "pt")
+            return this.pointsToPixels(name, value);
+
+        else if (unit == "em")
+            return this.emToPixels(el, value);
+
+        else if (unit == "%")
+            return this.percentToPixels(el, value);
+
+        else if (unit == "ex")
+            return this.exToPixels(el, value);
+
+        // TODO: add other units. Maybe create a better general way
+        // to calculate measurements in different units.
+    },
+
+    getMeasurementBox1: function(el, name)
+    {
+        var sufixes = ["Top", "Left", "Bottom", "Right"];
+        var result = [];
+
+        for(var i=0, sufix; sufix=sufixes[i]; i++)
+            result[i] = Math.round(this.getMeasurementInPixels(el, name + sufix));
+
+        return {top:result[0], left:result[1], bottom:result[2], right:result[3]};
+    },
+
+    getMeasurementBox: function(el, name)
+    {
+        var result = [];
+        var sufixes = name == "border" ?
+                ["TopWidth", "LeftWidth", "BottomWidth", "RightWidth"] :
+                ["Top", "Left", "Bottom", "Right"];
+
+        if (isIE)
+        {
+            var propName, cssValue;
+            var autoMargin = null;
+
+            for(var i=0, sufix; sufix=sufixes[i]; i++)
+            {
+                propName = name + sufix;
+
+                cssValue = el.currentStyle[propName] || el.style[propName];
+
+                if (cssValue == "auto")
+                {
+                    if (!autoMargin)
+                        autoMargin = this.getCSSAutoMarginBox(el);
+
+                    result[i] = autoMargin[sufix.toLowerCase()];
+                }
+                else
+                    result[i] = this.getMeasurementInPixels(el, propName);
+
+            }
+
+        }
+        else
+        {
+            for(var i=0, sufix; sufix=sufixes[i]; i++)
+                result[i] = this.getMeasurementInPixels(el, name + sufix);
+        }
+
+        return {top:result[0], left:result[1], bottom:result[2], right:result[3]};
+    },
+
+    getCSSAutoMarginBox: function(el)
+    {
+        if (isIE && " meta title input script link a ".indexOf(" "+el.nodeName.toLowerCase()+" ") != -1)
+            return {top:0, left:0, bottom:0, right:0};
+            /**/
+
+        if (isIE && " h1 h2 h3 h4 h5 h6 h7 ul p ".indexOf(" "+el.nodeName.toLowerCase()+" ") == -1)
+            return {top:0, left:0, bottom:0, right:0};
+            /**/
+
+        var offsetTop = 0;
+        if (false && isIEStantandMode)
+        {
+            var scrollSize = Firebug.browser.getWindowScrollSize();
+            offsetTop = scrollSize.height;
+        }
+
+        var box = this.document.createElement("div");
+        //box.style.cssText = "margin:0; padding:1px; border: 0; position:static; overflow:hidden; visibility: hidden;";
+        box.style.cssText = "margin:0; padding:1px; border: 0; visibility: hidden;";
+
+        var clone = el.cloneNode(false);
+        var text = this.document.createTextNode("&nbsp;");
+        clone.appendChild(text);
+
+        box.appendChild(clone);
+
+        this.document.body.appendChild(box);
+
+        var marginTop = clone.offsetTop - box.offsetTop - 1;
+        var marginBottom = box.offsetHeight - clone.offsetHeight - 2 - marginTop;
+
+        var marginLeft = clone.offsetLeft - box.offsetLeft - 1;
+        var marginRight = box.offsetWidth - clone.offsetWidth - 2 - marginLeft;
+
+        this.document.body.removeChild(box);
+
+        return {top:marginTop+offsetTop, left:marginLeft, bottom:marginBottom-offsetTop, right:marginRight};
+    },
+
+    getFontSizeInPixels: function(el)
+    {
+        var size = this.getMeasurement(el, "fontSize");
+
+        if (size.unit == "px") return size.value;
+
+        // get font size, the dirty way
+        var computeDirtyFontSize = function(el, calibration)
+        {
+            var div = this.document.createElement("div");
+            var divStyle = offscreenStyle;
+
+            if (calibration)
+                divStyle +=  " font-size:"+calibration+"px;";
+
+            div.style.cssText = divStyle;
+            div.innerHTML = "A";
+            el.appendChild(div);
+
+            var value = div.offsetHeight;
+            el.removeChild(div);
+            return value;
+        };
+
+        /*
+        var calibrationBase = 200;
+        var calibrationValue = computeDirtyFontSize(el, calibrationBase);
+        var rate = calibrationBase / calibrationValue;
+        /**/
+
+        // the "dirty technique" fails in some environments, so we're using a static value
+        // based in some tests.
+        var rate = 200 / 225;
+
+        var value = computeDirtyFontSize(el);
+
+        return value * rate;
+    },
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Unit Funtions
+
+    pointsToPixels: function(name, value, returnFloat)
+    {
+        var axis = /Top$|Bottom$/.test(name) ? "y" : "x";
+
+        var result = value * pixelsPerInch[axis] / 72;
+
+        return returnFloat ? result : Math.round(result);
+    },
+
+    emToPixels: function(el, value)
+    {
+        if (!el) return null;
+
+        var fontSize = this.getFontSizeInPixels(el);
+
+        return Math.round(value * fontSize);
+    },
+
+    exToPixels: function(el, value)
+    {
+        if (!el) return null;
+
+        // get ex value, the dirty way
+        var div = this.document.createElement("div");
+        div.style.cssText = offscreenStyle + "width:"+value + "ex;";
+
+        el.appendChild(div);
+        var value = div.offsetWidth;
+        el.removeChild(div);
+
+        return value;
+    },
+
+    percentToPixels: function(el, value)
+    {
+        if (!el) return null;
+
+        // get % value, the dirty way
+        var div = this.document.createElement("div");
+        div.style.cssText = offscreenStyle + "width:"+value + "%;";
+
+        el.appendChild(div);
+        var value = div.offsetWidth;
+        el.removeChild(div);
+
+        return value;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getStyle: isIE ? function(el, name)
+    {
+        return el.currentStyle[name] || el.style[name] || undefined;
+    }
+    : function(el, name)
+    {
+        return this.document.defaultView.getComputedStyle(el,null)[name]
+            || el.style[name] || undefined;
+    }
+
+};
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /**@scope ns-chrome*/ function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Globals
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Window Options
+
+var WindowDefaultOptions =
+    {
+        type: "frame",
+        id: "FirebugUI"
+        //height: 350 // obsolete
+    },
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Instantiated objects
+
+    commandLine,
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Interface Elements Cache
+
+    fbTop,
+    fbContent,
+    fbContentStyle,
+    fbBottom,
+    fbBtnInspect,
+
+    fbToolbar,
+
+    fbPanelBox1,
+    fbPanelBox1Style,
+    fbPanelBox2,
+    fbPanelBox2Style,
+    fbPanelBar2Box,
+    fbPanelBar2BoxStyle,
+
+    fbHSplitter,
+    fbVSplitter,
+    fbVSplitterStyle,
+
+    fbPanel1,
+    fbPanel1Style,
+    fbPanel2,
+    fbPanel2Style,
+
+    fbConsole,
+    fbConsoleStyle,
+    fbHTML,
+
+    fbCommandLine,
+    fbLargeCommandLine,
+    fbLargeCommandButtons,
+
+//* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Cached size values
+
+    topHeight,
+    topPartialHeight,
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    chromeRedrawSkipRate = isIE ? 75 : isOpera ? 80 : 75,
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    lastSelectedPanelName,
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    focusCommandLineState = 0,
+    lastFocusedPanelName,
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    lastHSplitterMouseMove = 0,
+    onHSplitterMouseMoveBuffer = null,
+    onHSplitterMouseMoveTimer = null,
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    lastVSplitterMouseMove = 0;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+
+// ************************************************************************************************
+// FirebugChrome
+
+FBL.defaultPersistedState =
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    isOpen: false,
+    height: 300,
+    sidePanelWidth: 350,
+
+    selectedPanelName: "Console",
+    selectedHTMLElementId: null,
+
+    htmlSelectionStack: []
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+};
+
+/**@namespace*/
+FBL.FirebugChrome =
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    //isOpen: false,
+    //height: 300,
+    //sidePanelWidth: 350,
+
+    //selectedPanelName: "Console",
+    //selectedHTMLElementId: null,
+
+    chromeMap: {},
+
+    htmlSelectionStack: [],
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    create: function()
+    {
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.create", "creating chrome window");
+
+        createChromeWindow();
+    },
+
+    initialize: function()
+    {
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("FirebugChrome.initialize", "initializing chrome window");
+
+        if (Env.chrome.type == "frame" || Env.chrome.type == "div")
+            ChromeMini.create(Env.chrome);
+
+        var chrome = Firebug.chrome = new Chrome(Env.chrome);
+        FirebugChrome.chromeMap[chrome.type] = chrome;
+
+        addGlobalEvent("keydown", onGlobalKeyDown);
+
+        if (Env.Options.enablePersistent && chrome.type == "popup")
+        {
+            // TODO: xxxpedro persist - revise chrome synchronization when in persistent mode
+            var frame = FirebugChrome.chromeMap.frame;
+            if (frame)
+                frame.close();
+
+            //chrome.reattach(frame, chrome);
+            //TODO: xxxpedro persist synchronize?
+            chrome.initialize();
+        }
+    },
+
+    clone: function(FBChrome)
+    {
+        for (var name in FBChrome)
+        {
+            var prop = FBChrome[name];
+            if (FBChrome.hasOwnProperty(name) && !isFunction(prop))
+            {
+                this[name] = prop;
+            }
+        }
+    }
+};
+
+
+
+// ************************************************************************************************
+// Chrome Window Creation
+
+var createChromeWindow = function(options)
+{
+    options = extend(WindowDefaultOptions, options || {});
+
+    //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Locals
+
+    var browserWin = Env.browser.window;
+    var browserContext = new Context(browserWin);
+    var prefs = Store.get("FirebugLite");
+    var persistedState = prefs && prefs.persistedState || defaultPersistedState;
+
+    var chrome = {},
+
+        context = options.context || Env.browser,
+
+        type = chrome.type = Env.Options.enablePersistent ?
+                "popup" :
+                options.type,
+
+        isChromeFrame = type == "frame",
+
+        useLocalSkin = Env.useLocalSkin,
+
+        url = useLocalSkin ?
+                Env.Location.skin :
+                "about:blank",
+
+        // document.body not available in XML+XSL documents in Firefox
+        body = context.document.getElementsByTagName("body")[0],
+
+        formatNode = function(node)
+        {
+            if (!Env.isDebugMode)
+            {
+                node.firebugIgnore = true;
+            }
+
+            var browserWinSize = browserContext.getWindowSize();
+            var height = persistedState.height || 300;
+
+            height = Math.min(browserWinSize.height, height);
+            height = Math.max(200, height);
+
+            node.style.border = "0";
+            node.style.visibility = "hidden";
+            node.style.zIndex = "2147483647"; // MAX z-index = 2147483647
+            node.style.position = noFixedPosition ? "absolute" : "fixed";
+            node.style.width = "100%"; // "102%"; IE auto margin bug
+            node.style.left = "0";
+            node.style.bottom = noFixedPosition ? "-1px" : "0";
+            node.style.height = height + "px";
+
+            // avoid flickering during chrome rendering
+            //if (isFirefox)
+            //    node.style.display = "none";
+        },
+
+        createChromeDiv = function()
+        {
+            //Firebug.Console.warn("Firebug Lite GUI is working in 'windowless mode'. It may behave slower and receive interferences from the page in which it is installed.");
+
+            var node = chrome.node = createGlobalElement("div"),
+                style = createGlobalElement("style"),
+
+                css = FirebugChrome.Skin.CSS
+                        /*
+                        .replace(/;/g, " !important;")
+                        .replace(/!important\s!important/g, "!important")
+                        .replace(/display\s*:\s*(\w+)\s*!important;/g, "display:$1;")*/,
+
+                        // reset some styles to minimize interference from the main page's style
+                rules = ".fbBody *{margin:0;padding:0;font-size:11px;line-height:13px;color:inherit;}" +
+                        // load the chrome styles
+                        css +
+                        // adjust some remaining styles
+                        ".fbBody #fbHSplitter{position:absolute !important;} .fbBody #fbHTML span{line-height:14px;} .fbBody .lineNo div{line-height:inherit !important;}";
+            /*
+            if (isIE)
+            {
+                // IE7 CSS bug (FbChrome table bigger than its parent div)
+                rules += ".fbBody table.fbChrome{position: static !important;}";
+            }/**/
+
+            style.type = "text/css";
+
+            if (style.styleSheet)
+                style.styleSheet.cssText = rules;
+            else
+                style.appendChild(context.document.createTextNode(rules));
+
+            document.getElementsByTagName("head")[0].appendChild(style);
+
+            node.className = "fbBody";
+            node.style.overflow = "hidden";
+            node.innerHTML = getChromeDivTemplate();
+
+            if (isIE)
+            {
+                // IE7 CSS bug (FbChrome table bigger than its parent div)
+                setTimeout(function(){
+                node.firstChild.style.height = "1px";
+                node.firstChild.style.position = "static";
+                },0);
+                /**/
+            }
+
+            formatNode(node);
+
+            body.appendChild(node);
+
+            chrome.window = window;
+            chrome.document = document;
+            onChromeLoad(chrome);
+        };
+
+    //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    try
+    {
+        //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // create the Chrome as a "div" (windowless mode)
+        if (type == "div")
+        {
+            createChromeDiv();
+            return;
+        }
+
+        //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // cretate the Chrome as an "iframe"
+        else if (isChromeFrame)
+        {
+            // Create the Chrome Frame
+            var node = chrome.node = createGlobalElement("iframe");
+            node.setAttribute("src", url);
+            node.setAttribute("frameBorder", "0");
+
+            formatNode(node);
+
+            body.appendChild(node);
+
+            // must set the id after appending to the document, otherwise will cause an
+            // strange error in IE, making the iframe load the page in which the bookmarklet
+            // was created (like getfirebug.com), before loading the injected UI HTML,
+            // generating an "Access Denied" error.
+            node.id = options.id;
+        }
+
+        //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // create the Chrome as a "popup"
+        else
+        {
+            var height = persistedState.popupHeight || 300;
+            var browserWinSize = browserContext.getWindowSize();
+
+            var browserWinLeft = typeof browserWin.screenX == "number" ?
+                    browserWin.screenX : browserWin.screenLeft;
+
+            var popupLeft = typeof persistedState.popupLeft == "number" ?
+                    persistedState.popupLeft : browserWinLeft;
+
+            var browserWinTop = typeof browserWin.screenY == "number" ?
+                    browserWin.screenY : browserWin.screenTop;
+
+            var popupTop = typeof persistedState.popupTop == "number" ?
+                    persistedState.popupTop :
+                    Math.max(
+                            0,
+                            Math.min(
+                                    browserWinTop + browserWinSize.height - height,
+                                    // Google Chrome bug
+                                    screen.availHeight - height - 61
+                                )
+                            );
+
+            var popupWidth = typeof persistedState.popupWidth == "number" ?
+                    persistedState.popupWidth :
+                    Math.max(
+                            0,
+                            Math.min(
+                                    browserWinSize.width,
+                                    // Opera opens popup in a new tab if it's too big!
+                                    screen.availWidth-10
+                                )
+                            );
+
+            var popupHeight = typeof persistedState.popupHeight == "number" ?
+                    persistedState.popupHeight : 300;
+
+            var options = [
+                    "true,top=", popupTop,
+                    ",left=", popupLeft,
+                    ",height=", popupHeight,
+                    ",width=", popupWidth,
+                    ",resizable"
+                ].join(""),
+
+                node = chrome.node = context.window.open(
+                    url,
+                    "popup",
+                    options
+                );
+
+            if (node)
+            {
+                try
+                {
+                    node.focus();
+                }
+                catch(E)
+                {
+                    alert("Firebug Error: Firebug popup was blocked.");
+                    return;
+                }
+            }
+            else
+            {
+                alert("Firebug Error: Firebug popup was blocked.");
+                return;
+            }
+        }
+
+        //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // Inject the interface HTML if it is not using the local skin
+
+        if (!useLocalSkin)
+        {
+            var tpl = getChromeTemplate(!isChromeFrame),
+                doc = isChromeFrame ? node.contentWindow.document : node.document;
+
+            doc.write(tpl);
+            doc.close();
+        }
+
+        //* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // Wait the Window to be loaded
+
+        var win,
+
+            waitDelay = useLocalSkin ? isChromeFrame ? 200 : 300 : 100,
+
+            waitForWindow = function()
+            {
+                if ( // Frame loaded... OR
+                     isChromeFrame && (win=node.contentWindow) &&
+                     node.contentWindow.document.getElementById("fbCommandLine") ||
+
+                     // Popup loaded
+                     !isChromeFrame && (win=node.window) && node.document &&
+                     node.document.getElementById("fbCommandLine") )
+                {
+                    chrome.window = win.window;
+                    chrome.document = win.document;
+
+                    // Prevent getting the wrong chrome height in FF when opening a popup
+                    setTimeout(function(){
+                        onChromeLoad(chrome);
+                    }, useLocalSkin ? 200 : 0);
+                }
+                else
+                    setTimeout(waitForWindow, waitDelay);
+            };
+
+        waitForWindow();
+    }
+    catch(e)
+    {
+        var msg = e.message || e;
+
+        if (/access/i.test(msg))
+        {
+            // Firebug Lite could not create a window for its Graphical User Interface due to
+            // a access restriction. This happens in some pages, when loading via bookmarklet.
+            // In such cases, the only way is to load the GUI in a "windowless mode".
+
+            if (isChromeFrame)
+                body.removeChild(node);
+            else if(type == "popup")
+                node.close();
+
+            // Load the GUI in a "windowless mode"
+            createChromeDiv();
+        }
+        else
+        {
+            alert("Firebug Error: Firebug GUI could not be created.");
+        }
+    }
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var onChromeLoad = function onChromeLoad(chrome)
+{
+    Env.chrome = chrome;
+
+    if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Chrome onChromeLoad", "chrome window loaded");
+
+    if (Env.Options.enablePersistent)
+    {
+        // TODO: xxxpedro persist - make better chrome synchronization when in persistent mode
+        Env.FirebugChrome = FirebugChrome;
+
+        chrome.window.Firebug = chrome.window.Firebug || {};
+        chrome.window.Firebug.SharedEnv = Env;
+
+        if (Env.isDevelopmentMode)
+        {
+            Env.browser.window.FBDev.loadChromeApplication(chrome);
+        }
+        else
+        {
+            var doc = chrome.document;
+            var script = doc.createElement("script");
+            script.src = Env.Location.app + "#remote,persist";
+            doc.getElementsByTagName("head")[0].appendChild(script);
+        }
+    }
+    else
+    {
+        if (chrome.type == "frame" || chrome.type == "div")
+        {
+            // initialize the chrome application
+            setTimeout(function(){
+                FBL.Firebug.initialize();
+            },0);
+        }
+        else if (chrome.type == "popup")
+        {
+            var oldChrome = FirebugChrome.chromeMap.frame;
+
+            var newChrome = new Chrome(chrome);
+
+            // TODO: xxxpedro sync detach reattach attach
+            dispatch(newChrome.panelMap, "detach", [oldChrome, newChrome]);
+
+            newChrome.reattach(oldChrome, newChrome);
+        }
+    }
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var getChromeDivTemplate = function()
+{
+    return FirebugChrome.Skin.HTML;
+};
+
+var getChromeTemplate = function(isPopup)
+{
+    var tpl = FirebugChrome.Skin;
+    var r = [], i = -1;
+
+    r[++i] = '<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/DTD/strict.dtd">';
+    r[++i] = '<html><head><title>';
+    r[++i] = Firebug.version;
+
+    /*
+    r[++i] = '</title><link href="';
+    r[++i] = Env.Location.skinDir + 'firebug.css';
+    r[++i] = '" rel="stylesheet" type="text/css" />';
+    /**/
+
+    r[++i] = '</title><style>html,body{margin:0;padding:0;overflow:hidden;}';
+    r[++i] = tpl.CSS;
+    r[++i] = '</style>';
+    /**/
+
+    r[++i] = '</head><body class="fbBody' + (isPopup ? ' FirebugPopup' : '') + '">';
+    r[++i] = tpl.HTML;
+    r[++i] = '</body></html>';
+
+    return r.join("");
+};
+
+
+// ************************************************************************************************
+// Chrome Class
+
+/**@class*/
+var Chrome = function Chrome(chrome)
+{
+    var type = chrome.type;
+    var Base = type == "frame" || type == "div" ? ChromeFrameBase : ChromePopupBase;
+
+    append(this, Base);   // inherit from base class (ChromeFrameBase or ChromePopupBase)
+    append(this, chrome); // inherit chrome window properties
+    append(this, new Context(chrome.window)); // inherit from Context class
+
+    FirebugChrome.chromeMap[type] = this;
+    Firebug.chrome = this;
+    Env.chrome = chrome.window;
+
+    this.commandLineVisible = false;
+    this.sidePanelVisible = false;
+
+    this.create();
+
+    return this;
+};
+
+// ************************************************************************************************
+// ChromeBase
+
+/**
+ * @namespace
+ * @extends FBL.Controller
+ * @extends FBL.PanelBar
+ **/
+var ChromeBase = {};
+append(ChromeBase, Controller);
+append(ChromeBase, PanelBar);
+append(ChromeBase,
+/**@extend ns-chrome-ChromeBase*/
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // inherited properties
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // inherited from createChrome function
+
+    node: null,
+    type: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // inherited from Context.prototype
+
+    document: null,
+    window: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // value properties
+
+    sidePanelVisible: false,
+    commandLineVisible: false,
+    largeCommandLineVisible: false,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // object properties
+
+    inspectButton: null,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    create: function()
+    {
+        PanelBar.create.call(this);
+
+        if (Firebug.Inspector)
+            this.inspectButton = new Button({
+                type: "toggle",
+                element: $("fbChrome_btInspect"),
+                owner: Firebug.Inspector,
+
+                onPress: Firebug.Inspector.startInspecting,
+                onUnpress: Firebug.Inspector.stopInspecting
+            });
+    },
+
+    destroy: function()
+    {
+        if(Firebug.Inspector)
+            this.inspectButton.destroy();
+
+        PanelBar.destroy.call(this);
+
+        this.shutdown();
+    },
+
+    testMenu: function()
+    {
+        var firebugMenu = new Menu(
+        {
+            id: "fbFirebugMenu",
+
+            items:
+            [
+                {
+                    label: "Open Firebug",
+                    type: "shortcut",
+                    key: isFirefox ? "Shift+F12" : "F12",
+                    checked: true,
+                    command: "toggleChrome"
+                },
+                {
+                    label: "Open Firebug in New Window",
+                    type: "shortcut",
+                    key: isFirefox ? "Ctrl+Shift+F12" : "Ctrl+F12",
+                    command: "openPopup"
+                },
+                {
+                    label: "Inspect Element",
+                    type: "shortcut",
+                    key: "Ctrl+Shift+C",
+                    command: "toggleInspect"
+                },
+                {
+                    label: "Command Line",
+                    type: "shortcut",
+                    key: "Ctrl+Shift+L",
+                    command: "focusCommandLine"
+                },
+                "-",
+                {
+                    label: "Options",
+                    type: "group",
+                    child: "fbFirebugOptionsMenu"
+                },
+                "-",
+                {
+                    label: "Firebug Lite Website...",
+                    command: "visitWebsite"
+                },
+                {
+                    label: "Discussion Group...",
+                    command: "visitDiscussionGroup"
+                },
+                {
+                    label: "Issue Tracker...",
+                    command: "visitIssueTracker"
+                }
+            ],
+
+            onHide: function()
+            {
+                iconButton.restore();
+            },
+
+            toggleChrome: function()
+            {
+                Firebug.chrome.toggle();
+            },
+
+            openPopup: function()
+            {
+                Firebug.chrome.toggle(true, true);
+            },
+
+            toggleInspect: function()
+            {
+                Firebug.Inspector.toggleInspect();
+            },
+
+            focusCommandLine: function()
+            {
+                Firebug.chrome.focusCommandLine();
+            },
+
+            visitWebsite: function()
+            {
+                this.visit("http://getfirebug.com/lite.html");
+            },
+
+            visitDiscussionGroup: function()
+            {
+                this.visit("http://groups.google.com/group/firebug");
+            },
+
+            visitIssueTracker: function()
+            {
+                this.visit("http://code.google.com/p/fbug/issues/list");
+            },
+
+            visit: function(url)
+            {
+                window.open(url);
+            }
+
+        });
+
+        /**@private*/
+        var firebugOptionsMenu =
+        {
+            id: "fbFirebugOptionsMenu",
+
+            getItems: function()
+            {
+                var cookiesDisabled = !Firebug.saveCookies;
+
+                return [
+                    {
+                        label: "Start Opened",
+                        type: "checkbox",
+                        value: "startOpened",
+                        checked: Firebug.startOpened,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Start in New Window",
+                        type: "checkbox",
+                        value: "startInNewWindow",
+                        checked: Firebug.startInNewWindow,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Show Icon When Hidden",
+                        type: "checkbox",
+                        value: "showIconWhenHidden",
+                        checked: Firebug.showIconWhenHidden,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Override Console Object",
+                        type: "checkbox",
+                        value: "overrideConsole",
+                        checked: Firebug.overrideConsole,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Ignore Firebug Elements",
+                        type: "checkbox",
+                        value: "ignoreFirebugElements",
+                        checked: Firebug.ignoreFirebugElements,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Disable When Firebug Active",
+                        type: "checkbox",
+                        value: "disableWhenFirebugActive",
+                        checked: Firebug.disableWhenFirebugActive,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Disable XHR Listener",
+                        type: "checkbox",
+                        value: "disableXHRListener",
+                        checked: Firebug.disableXHRListener,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Disable Resource Fetching",
+                        type: "checkbox",
+                        value: "disableResourceFetching",
+                        checked: Firebug.disableResourceFetching,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Enable Trace Mode",
+                        type: "checkbox",
+                        value: "enableTrace",
+                        checked: Firebug.enableTrace,
+                        disabled: cookiesDisabled
+                    },
+                    {
+                        label: "Enable Persistent Mode (experimental)",
+                        type: "checkbox",
+                        value: "enablePersistent",
+                        checked: Firebug.enablePersistent,
+                        disabled: cookiesDisabled
+                    },
+                    "-",
+                    {
+                        label: "Reset All Firebug Options",
+                        command: "restorePrefs",
+                        disabled: cookiesDisabled
+                    }
+                ];
+            },
+
+            onCheck: function(target, value, checked)
+            {
+                Firebug.setPref(value, checked);
+            },
+
+            restorePrefs: function(target)
+            {
+                Firebug.erasePrefs();
+
+                if (target)
+                    this.updateMenu(target);
+            },
+
+            updateMenu: function(target)
+            {
+                var options = getElementsByClass(target.parentNode, "fbMenuOption");
+
+                var firstOption = options[0];
+                var enabled = Firebug.saveCookies;
+                if (enabled)
+                    Menu.check(firstOption);
+                else
+                    Menu.uncheck(firstOption);
+
+                if (enabled)
+                    Menu.check(options[0]);
+                else
+                    Menu.uncheck(options[0]);
+
+                for (var i = 1, length = options.length; i < length; i++)
+                {
+                    var option = options[i];
+
+                    var value = option.getAttribute("value");
+                    var pref = Firebug[value];
+
+                    if (pref)
+                        Menu.check(option);
+                    else
+                        Menu.uncheck(option);
+
+                    if (enabled)
+                        Menu.enable(option);
+                    else
+                        Menu.disable(option);
+                }
+            }
+        };
+
+        Menu.register(firebugOptionsMenu);
+
+        var menu = firebugMenu;
+
+        var testMenuClick = function(event)
+        {
+            //console.log("testMenuClick");
+            cancelEvent(event, true);
+
+            var target = event.target || event.srcElement;
+
+            if (menu.isVisible)
+                menu.hide();
+            else
+            {
+                var offsetLeft = isIE6 ? 1 : -4,  // IE6 problem with fixed position
+
+                    chrome = Firebug.chrome,
+
+                    box = chrome.getElementBox(target),
+
+                    offset = chrome.type == "div" ?
+                            chrome.getElementPosition(chrome.node) :
+                            {top: 0, left: 0};
+
+                menu.show(
+                            box.left + offsetLeft - offset.left,
+                            box.top + box.height -5 - offset.top
+                        );
+            }
+
+            return false;
+        };
+
+        var iconButton = new IconButton({
+            type: "toggle",
+            element: $("fbFirebugButton"),
+
+            onClick: testMenuClick
+        });
+
+        iconButton.initialize();
+
+        //addEvent($("fbToolbarIcon"), "click", testMenuClick);
+    },
+
+    initialize: function()
+    {
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        if (Env.bookmarkletOutdated)
+            Firebug.Console.logFormatted([
+                  "A new bookmarklet version is available. " +
+                  "Please visit http://getfirebug.com/firebuglite#Install and update it."
+                ], Firebug.context, "warn");
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        if (Firebug.Console)
+            Firebug.Console.flush();
+
+        if (Firebug.Trace)
+            FBTrace.flush(Firebug.Trace);
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.chrome.initialize", "initializing chrome application");
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // initialize inherited classes
+        Controller.initialize.call(this);
+        PanelBar.initialize.call(this);
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // create the interface elements cache
+
+        fbTop = $("fbTop");
+        fbContent = $("fbContent");
+        fbContentStyle = fbContent.style;
+        fbBottom = $("fbBottom");
+        fbBtnInspect = $("fbBtnInspect");
+
+        fbToolbar = $("fbToolbar");
+
+        fbPanelBox1 = $("fbPanelBox1");
+        fbPanelBox1Style = fbPanelBox1.style;
+        fbPanelBox2 = $("fbPanelBox2");
+        fbPanelBox2Style = fbPanelBox2.style;
+        fbPanelBar2Box = $("fbPanelBar2Box");
+        fbPanelBar2BoxStyle = fbPanelBar2Box.style;
+
+        fbHSplitter = $("fbHSplitter");
+        fbVSplitter = $("fbVSplitter");
+        fbVSplitterStyle = fbVSplitter.style;
+
+        fbPanel1 = $("fbPanel1");
+        fbPanel1Style = fbPanel1.style;
+        fbPanel2 = $("fbPanel2");
+        fbPanel2Style = fbPanel2.style;
+
+        fbConsole = $("fbConsole");
+        fbConsoleStyle = fbConsole.style;
+        fbHTML = $("fbHTML");
+
+        fbCommandLine = $("fbCommandLine");
+        fbLargeCommandLine = $("fbLargeCommandLine");
+        fbLargeCommandButtons = $("fbLargeCommandButtons");
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // static values cache
+        topHeight = fbTop.offsetHeight;
+        topPartialHeight = fbToolbar.offsetHeight;
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+        disableTextSelection($("fbToolbar"));
+        disableTextSelection($("fbPanelBarBox"));
+        disableTextSelection($("fbPanelBar1"));
+        disableTextSelection($("fbPanelBar2"));
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // Add the "javascript:void(0)" href attributes used to make the hover effect in IE6
+        if (isIE6 && Firebug.Selector)
+        {
+            // TODO: xxxpedro change to getElementsByClass
+            var as = $$(".fbHover");
+            for (var i=0, a; a=as[i]; i++)
+            {
+                a.setAttribute("href", "javascript:void(0)");
+            }
+        }
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // initialize all panels
+        /*
+        var panelMap = Firebug.panelTypes;
+        for (var i=0, p; p=panelMap[i]; i++)
+        {
+            if (!p.parentPanel)
+            {
+                this.addPanel(p.prototype.name);
+            }
+        }
+        /**/
+
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+
+        if(Firebug.Inspector)
+            this.inspectButton.initialize();
+
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+
+        this.addController(
+            [$("fbLargeCommandLineIcon"), "click", this.showLargeCommandLine]
+        );
+
+        // ************************************************************************************************
+
+        // Select the first registered panel
+        // TODO: BUG IE7
+        var self = this;
+        setTimeout(function(){
+            self.selectPanel(Firebug.context.persistedState.selectedPanelName);
+
+            if (Firebug.context.persistedState.selectedPanelName == "Console" && Firebug.CommandLine)
+                Firebug.chrome.focusCommandLine();
+        },0);
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        //this.draw();
+
+
+
+
+
+
+
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+        var onPanelMouseDown = function onPanelMouseDown(event)
+        {
+            //console.log("onPanelMouseDown", event.target || event.srcElement, event);
+
+            var target = event.target || event.srcElement;
+
+            if (FBL.isLeftClick(event))
+            {
+                var editable = FBL.getAncestorByClass(target, "editable");
+
+                // if an editable element has been clicked then start editing
+                if (editable)
+                {
+                    Firebug.Editor.startEditing(editable);
+                    FBL.cancelEvent(event);
+                }
+                // if any other element has been clicked then stop editing
+                else
+                {
+                    if (!hasClass(target, "textEditorInner"))
+                        Firebug.Editor.stopEditing();
+                }
+            }
+            else if (FBL.isMiddleClick(event) && Firebug.getRepNode(target))
+            {
+                // Prevent auto-scroll when middle-clicking a rep object
+                FBL.cancelEvent(event);
+            }
+        };
+
+        Firebug.getElementPanel = function(element)
+        {
+            var panelNode = getAncestorByClass(element, "fbPanel");
+            var id = panelNode.id.substr(2);
+
+            var panel = Firebug.chrome.panelMap[id];
+
+            if (!panel)
+            {
+                if (Firebug.chrome.selectedPanel.sidePanelBar)
+                    panel = Firebug.chrome.selectedPanel.sidePanelBar.panelMap[id];
+            }
+
+            return panel;
+        };
+
+
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+        // TODO: xxxpedro port to Firebug
+
+        // Improved window key code event listener. Only one "keydown" event will be attached
+        // to the window, and the onKeyCodeListen() function will delegate which listeners
+        // should be called according to the event.keyCode fired.
+        var onKeyCodeListenersMap = [];
+        var onKeyCodeListen = function(event)
+        {
+            for (var keyCode in onKeyCodeListenersMap)
+            {
+                var listeners = onKeyCodeListenersMap[keyCode];
+
+                for (var i = 0, listener; listener = listeners[i]; i++)
+                {
+                    var filter = listener.filter || FBL.noKeyModifiers;
+
+                    if (event.keyCode == keyCode && (!filter || filter(event)))
+                    {
+                        listener.listener();
+                        FBL.cancelEvent(event, true);
+                        return false;
+                    }
+                }
+            }
+        };
+
+        addEvent(Firebug.chrome.document, "keydown", onKeyCodeListen);
+
+        /**
+         * @name keyCodeListen
+         * @memberOf FBL.FirebugChrome
+         */
+        Firebug.chrome.keyCodeListen = function(key, filter, listener, capture)
+        {
+            var keyCode = KeyEvent["DOM_VK_"+key];
+
+            if (!onKeyCodeListenersMap[keyCode])
+                onKeyCodeListenersMap[keyCode] = [];
+
+            onKeyCodeListenersMap[keyCode].push({
+                filter: filter,
+                listener: listener
+            });
+
+            return keyCode;
+        };
+
+        /**
+         * @name keyIgnore
+         * @memberOf FBL.FirebugChrome
+         */
+        Firebug.chrome.keyIgnore = function(keyCode)
+        {
+            onKeyCodeListenersMap[keyCode] = null;
+            delete onKeyCodeListenersMap[keyCode];
+        };
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+        /**/
+        // move to shutdown
+        //removeEvent(Firebug.chrome.document, "keydown", listener[0]);
+
+
+        /*
+        Firebug.chrome.keyCodeListen = function(key, filter, listener, capture)
+        {
+            if (!filter)
+                filter = FBL.noKeyModifiers;
+
+            var keyCode = KeyEvent["DOM_VK_"+key];
+
+            var fn = function fn(event)
+            {
+                if (event.keyCode == keyCode && (!filter || filter(event)))
+                {
+                    listener();
+                    FBL.cancelEvent(event, true);
+                    return false;
+                }
+            }
+
+            addEvent(Firebug.chrome.document, "keydown", fn);
+
+            return [fn, capture];
+        };
+
+        Firebug.chrome.keyIgnore = function(listener)
+        {
+            removeEvent(Firebug.chrome.document, "keydown", listener[0]);
+        };
+        /**/
+
+
+        this.addController(
+                [fbPanel1, "mousedown", onPanelMouseDown],
+                [fbPanel2, "mousedown", onPanelMouseDown]
+             );
+/**/
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+
+        // menus can be used without domplate
+        if (FBL.domplate)
+            this.testMenu();
+        /**/
+
+        //test XHR
+        /*
+        setTimeout(function(){
+
+        FBL.Ajax.request({url: "../content/firebug/boot.js"});
+        FBL.Ajax.request({url: "../content/firebug/boot.js.invalid"});
+
+        },1000);
+        /**/
+    },
+
+    shutdown: function()
+    {
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+
+        if(Firebug.Inspector)
+            this.inspectButton.shutdown();
+
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+        // ************************************************************************************************
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+        // remove disableTextSelection event handlers
+        restoreTextSelection($("fbToolbar"));
+        restoreTextSelection($("fbPanelBarBox"));
+        restoreTextSelection($("fbPanelBar1"));
+        restoreTextSelection($("fbPanelBar2"));
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // shutdown inherited classes
+        Controller.shutdown.call(this);
+        PanelBar.shutdown.call(this);
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // Remove the interface elements cache (this must happen after calling
+        // the shutdown method of all dependent components to avoid errors)
+
+        fbTop = null;
+        fbContent = null;
+        fbContentStyle = null;
+        fbBottom = null;
+        fbBtnInspect = null;
+
+        fbToolbar = null;
+
+        fbPanelBox1 = null;
+        fbPanelBox1Style = null;
+        fbPanelBox2 = null;
+        fbPanelBox2Style = null;
+        fbPanelBar2Box = null;
+        fbPanelBar2BoxStyle = null;
+
+        fbHSplitter = null;
+        fbVSplitter = null;
+        fbVSplitterStyle = null;
+
+        fbPanel1 = null;
+        fbPanel1Style = null;
+        fbPanel2 = null;
+
+        fbConsole = null;
+        fbConsoleStyle = null;
+        fbHTML = null;
+
+        fbCommandLine = null;
+        fbLargeCommandLine = null;
+        fbLargeCommandButtons = null;
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // static values cache
+
+        topHeight = null;
+        topPartialHeight = null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    toggle: function(forceOpen, popup)
+    {
+        if(popup)
+        {
+            this.detach();
+        }
+        else
+        {
+            if (isOpera && Firebug.chrome.type == "popup" && Firebug.chrome.node.closed)
+            {
+                var frame = FirebugChrome.chromeMap.frame;
+                frame.reattach();
+
+                FirebugChrome.chromeMap.popup = null;
+
+                frame.open();
+
+                return;
+            }
+
+            // If the context is a popup, ignores the toggle process
+            if (Firebug.chrome.type == "popup") return;
+
+            var shouldOpen = forceOpen || !Firebug.context.persistedState.isOpen;
+
+            if(shouldOpen)
+               this.open();
+            else
+               this.close();
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    detach: function()
+    {
+        if(!FirebugChrome.chromeMap.popup)
+        {
+            this.close();
+            createChromeWindow({type: "popup"});
+        }
+    },
+
+    reattach: function(oldChrome, newChrome)
+    {
+        Firebug.browser.window.Firebug = Firebug;
+
+        // chrome synchronization
+        var newPanelMap = newChrome.panelMap;
+        var oldPanelMap = oldChrome.panelMap;
+
+        var panel;
+        for(var name in newPanelMap)
+        {
+            // TODO: xxxpedro innerHTML
+            panel = newPanelMap[name];
+            if (panel.options.innerHTMLSync)
+                panel.panelNode.innerHTML = oldPanelMap[name].panelNode.innerHTML;
+        }
+
+        Firebug.chrome = newChrome;
+
+        // TODO: xxxpedro sync detach reattach attach
+        //dispatch(Firebug.chrome.panelMap, "detach", [oldChrome, newChrome]);
+
+        if (newChrome.type == "popup")
+        {
+            newChrome.initialize();
+            //dispatch(Firebug.modules, "initialize", []);
+        }
+        else
+        {
+            // TODO: xxxpedro only needed in persistent
+            // should use FirebugChrome.clone, but popup FBChrome
+            // isn't acessible
+            Firebug.context.persistedState.selectedPanelName = oldChrome.selectedPanel.name;
+        }
+
+        dispatch(newPanelMap, "reattach", [oldChrome, newChrome]);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    draw: function()
+    {
+        var size = this.getSize();
+
+        // Height related values
+        var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0,
+
+            y = Math.max(size.height /* chrome height */, topHeight),
+
+            heightValue = Math.max(y - topHeight - commandLineHeight /* fixed height */, 0),
+
+            height = heightValue + "px",
+
+            // Width related values
+            sideWidthValue = Firebug.chrome.sidePanelVisible ? Firebug.context.persistedState.sidePanelWidth : 0,
+
+            width = Math.max(size.width /* chrome width */ - sideWidthValue, 0) + "px";
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // Height related rendering
+        fbPanelBox1Style.height = height;
+        fbPanel1Style.height = height;
+
+        if (isIE || isOpera)
+        {
+            // Fix IE and Opera problems with auto resizing the verticall splitter
+            fbVSplitterStyle.height = Math.max(y - topPartialHeight - commandLineHeight, 0) + "px";
+        }
+        //xxxpedro FF2 only?
+        /*
+        else if (isFirefox)
+        {
+            // Fix Firefox problem with table rows with 100% height (fit height)
+            fbContentStyle.maxHeight = Math.max(y - fixedHeight, 0)+ "px";
+        }/**/
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // Width related rendering
+        fbPanelBox1Style.width = width;
+        fbPanel1Style.width = width;
+
+        // SidePanel rendering
+        if (Firebug.chrome.sidePanelVisible)
+        {
+            sideWidthValue = Math.max(sideWidthValue - 6, 0);
+
+            var sideWidth = sideWidthValue + "px";
+
+            fbPanelBox2Style.width = sideWidth;
+
+            fbVSplitterStyle.right = sideWidth;
+
+            if (Firebug.chrome.largeCommandLineVisible)
+            {
+                fbLargeCommandLine = $("fbLargeCommandLine");
+
+                fbLargeCommandLine.style.height = heightValue - 4 + "px";
+                fbLargeCommandLine.style.width = sideWidthValue - 2 + "px";
+
+                fbLargeCommandButtons = $("fbLargeCommandButtons");
+                fbLargeCommandButtons.style.width = sideWidth;
+            }
+            else
+            {
+                fbPanel2Style.height = height;
+                fbPanel2Style.width = sideWidth;
+
+                fbPanelBar2BoxStyle.width = sideWidth;
+            }
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getSize: function()
+    {
+        return this.type == "div" ?
+            {
+                height: this.node.offsetHeight,
+                width: this.node.offsetWidth
+            }
+            :
+            this.getWindowSize();
+    },
+
+    resize: function()
+    {
+        var self = this;
+
+        // avoid partial resize when maximizing window
+        setTimeout(function(){
+            self.draw();
+
+            if (noFixedPosition && (self.type == "frame" || self.type == "div"))
+                self.fixIEPosition();
+        }, 0);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    layout: function(panel)
+    {
+        if (FBTrace.DBG_CHROME) FBTrace.sysout("Chrome.layout", "");
+
+        var options = panel.options;
+
+        changeCommandLineVisibility(options.hasCommandLine);
+        changeSidePanelVisibility(panel.hasSidePanel);
+
+        Firebug.chrome.draw();
+    },
+
+    showLargeCommandLine: function(hideToggleIcon)
+    {
+        var chrome = Firebug.chrome;
+
+        if (!chrome.largeCommandLineVisible)
+        {
+            chrome.largeCommandLineVisible = true;
+
+            if (chrome.selectedPanel.options.hasCommandLine)
+            {
+                if (Firebug.CommandLine)
+                    Firebug.CommandLine.blur();
+
+                changeCommandLineVisibility(false);
+            }
+
+            changeSidePanelVisibility(true);
+
+            fbLargeCommandLine.style.display = "block";
+            fbLargeCommandButtons.style.display = "block";
+
+            fbPanel2Style.display = "none";
+            fbPanelBar2BoxStyle.display = "none";
+
+            chrome.draw();
+
+            fbLargeCommandLine.focus();
+
+            if (Firebug.CommandLine)
+                Firebug.CommandLine.setMultiLine(true);
+        }
+    },
+
+    hideLargeCommandLine: function()
+    {
+        if (Firebug.chrome.largeCommandLineVisible)
+        {
+            Firebug.chrome.largeCommandLineVisible = false;
+
+            if (Firebug.CommandLine)
+                Firebug.CommandLine.setMultiLine(false);
+
+            fbLargeCommandLine.blur();
+
+            fbPanel2Style.display = "block";
+            fbPanelBar2BoxStyle.display = "block";
+
+            fbLargeCommandLine.style.display = "none";
+            fbLargeCommandButtons.style.display = "none";
+
+            changeSidePanelVisibility(false);
+
+            if (Firebug.chrome.selectedPanel.options.hasCommandLine)
+                changeCommandLineVisibility(true);
+
+            Firebug.chrome.draw();
+
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    focusCommandLine: function()
+    {
+        var selectedPanelName = this.selectedPanel.name, panelToSelect;
+
+        if (focusCommandLineState == 0 || selectedPanelName != "Console")
+        {
+            focusCommandLineState = 0;
+            lastFocusedPanelName = selectedPanelName;
+
+            panelToSelect = "Console";
+        }
+        if (focusCommandLineState == 1)
+        {
+            panelToSelect = lastFocusedPanelName;
+        }
+
+        this.selectPanel(panelToSelect);
+
+        try
+        {
+            if (Firebug.CommandLine)
+            {
+                if (panelToSelect == "Console")
+                    Firebug.CommandLine.focus();
+                else
+                    Firebug.CommandLine.blur();
+            }
+        }
+        catch(e)
+        {
+            //TODO: xxxpedro trace error
+        }
+
+        focusCommandLineState = ++focusCommandLineState % 2;
+    }
+
+});
+
+// ************************************************************************************************
+// ChromeFrameBase
+
+/**
+ * @namespace
+ * @extends ns-chrome-ChromeBase
+ */
+var ChromeFrameBase = extend(ChromeBase,
+/**@extend ns-chrome-ChromeFrameBase*/
+{
+    create: function()
+    {
+        ChromeBase.create.call(this);
+
+        // restore display for the anti-flicker trick
+        if (isFirefox)
+            this.node.style.display = "block";
+
+        if (Env.Options.startInNewWindow)
+        {
+            this.close();
+            this.toggle(true, true);
+            return;
+        }
+
+        if (Env.Options.startOpened)
+            this.open();
+        else
+            this.close();
+    },
+
+    destroy: function()
+    {
+        var size = Firebug.chrome.getWindowSize();
+
+        Firebug.context.persistedState.height = size.height;
+
+        if (Firebug.saveCookies)
+            Firebug.savePrefs();
+
+        removeGlobalEvent("keydown", onGlobalKeyDown);
+
+        ChromeBase.destroy.call(this);
+
+        this.document = null;
+        delete this.document;
+
+        this.window = null;
+        delete this.window;
+
+        this.node.parentNode.removeChild(this.node);
+        this.node = null;
+        delete this.node;
+    },
+
+    initialize: function()
+    {
+        //FBTrace.sysout("Frame", "initialize();")
+        ChromeBase.initialize.call(this);
+
+        this.addController(
+            [Firebug.browser.window, "resize", this.resize],
+            [$("fbWindow_btClose"), "click", this.close],
+            [$("fbWindow_btDetach"), "click", this.detach],
+            [$("fbWindow_btDeactivate"), "click", this.deactivate]
+        );
+
+        if (!Env.Options.enablePersistent)
+            this.addController([Firebug.browser.window, "unload", Firebug.shutdown]);
+
+        if (noFixedPosition)
+        {
+            this.addController(
+                [Firebug.browser.window, "scroll", this.fixIEPosition]
+            );
+        }
+
+        fbVSplitter.onmousedown = onVSplitterMouseDown;
+        fbHSplitter.onmousedown = onHSplitterMouseDown;
+
+        this.isInitialized = true;
+    },
+
+    shutdown: function()
+    {
+        fbVSplitter.onmousedown = null;
+        fbHSplitter.onmousedown = null;
+
+        ChromeBase.shutdown.apply(this);
+
+        this.isInitialized = false;
+    },
+
+    reattach: function()
+    {
+        var frame = FirebugChrome.chromeMap.frame;
+
+        ChromeBase.reattach(FirebugChrome.chromeMap.popup, this);
+    },
+
+    open: function()
+    {
+        if (!Firebug.context.persistedState.isOpen)
+        {
+            Firebug.context.persistedState.isOpen = true;
+
+            if (Env.isChromeExtension)
+                localStorage.setItem("Firebug", "1,1");
+
+            var node = this.node;
+
+            node.style.visibility = "hidden"; // Avoid flickering
+
+            if (Firebug.showIconWhenHidden)
+            {
+                if (ChromeMini.isInitialized)
+                {
+                    ChromeMini.shutdown();
+                }
+
+            }
+            else
+                node.style.display = "block";
+
+            var main = $("fbChrome");
+
+            // IE6 throws an error when setting this property! why?
+            //main.style.display = "table";
+            main.style.display = "";
+
+            var self = this;
+                /// TODO: xxxpedro FOUC
+                node.style.visibility = "visible";
+            setTimeout(function(){
+                ///node.style.visibility = "visible";
+
+                //dispatch(Firebug.modules, "initialize", []);
+                self.initialize();
+
+                if (noFixedPosition)
+                    self.fixIEPosition();
+
+                self.draw();
+
+            }, 10);
+        }
+    },
+
+    close: function()
+    {
+        if (Firebug.context.persistedState.isOpen)
+        {
+            if (this.isInitialized)
+            {
+                //dispatch(Firebug.modules, "shutdown", []);
+                this.shutdown();
+            }
+
+            Firebug.context.persistedState.isOpen = false;
+
+            if (Env.isChromeExtension)
+                localStorage.setItem("Firebug", "1,0");
+
+            var node = this.node;
+
+            if (Firebug.showIconWhenHidden)
+            {
+                node.style.visibility = "hidden"; // Avoid flickering
+
+                // TODO: xxxpedro - persist IE fixed?
+                var main = $("fbChrome", FirebugChrome.chromeMap.frame.document);
+                main.style.display = "none";
+
+                ChromeMini.initialize();
+
+                node.style.visibility = "visible";
+            }
+            else
+                node.style.display = "none";
+        }
+    },
+
+    deactivate: function()
+    {
+        // if it is running as a Chrome extension, dispatch a message to the extension signaling
+        // that Firebug should be deactivated for the current tab
+        if (Env.isChromeExtension)
+        {
+            localStorage.removeItem("Firebug");
+            Firebug.GoogleChrome.dispatch("FB_deactivate");
+
+            // xxxpedro problem here regarding Chrome extension. We can't deactivate the whole
+            // app, otherwise it won't be able to be reactivated without reloading the page.
+            // but we need to stop listening global keys, otherwise the key activation won't work.
+            Firebug.chrome.close();
+        }
+        else
+        {
+            Firebug.shutdown();
+        }
+    },
+
+    fixIEPosition: function()
+    {
+        // fix IE problem with offset when not in fullscreen mode
+        var doc = this.document;
+        var offset = isIE ? doc.body.clientTop || doc.documentElement.clientTop: 0;
+
+        var size = Firebug.browser.getWindowSize();
+        var scroll = Firebug.browser.getWindowScrollPosition();
+        var maxHeight = size.height;
+        var height = this.node.offsetHeight;
+
+        var bodyStyle = doc.body.currentStyle;
+
+        this.node.style.top = maxHeight - height + scroll.top + "px";
+
+        if ((this.type == "frame" || this.type == "div") &&
+            (bodyStyle.marginLeft || bodyStyle.marginRight))
+        {
+            this.node.style.width = size.width + "px";
+        }
+
+        if (fbVSplitterStyle)
+            fbVSplitterStyle.right = Firebug.context.persistedState.sidePanelWidth + "px";
+
+        this.draw();
+    }
+
+});
+
+
+// ************************************************************************************************
+// ChromeMini
+
+/**
+ * @namespace
+ * @extends FBL.Controller
+ */
+var ChromeMini = extend(Controller,
+/**@extend ns-chrome-ChromeMini*/
+{
+    create: function(chrome)
+    {
+        append(this, chrome);
+        this.type = "mini";
+    },
+
+    initialize: function()
+    {
+        Controller.initialize.apply(this);
+
+        var doc = FirebugChrome.chromeMap.frame.document;
+
+        var mini = $("fbMiniChrome", doc);
+        mini.style.display = "block";
+
+        var miniIcon = $("fbMiniIcon", doc);
+        var width = miniIcon.offsetWidth + 10;
+        miniIcon.title = "Open " + Firebug.version;
+
+        var errors = $("fbMiniErrors", doc);
+        if (errors.offsetWidth)
+            width += errors.offsetWidth + 10;
+
+        var node = this.node;
+        node.style.height = "27px";
+        node.style.width = width + "px";
+        node.style.left = "";
+        node.style.right = 0;
+
+        if (this.node.nodeName.toLowerCase() == "iframe")
+        {
+            node.setAttribute("allowTransparency", "true");
+            this.document.body.style.backgroundColor = "transparent";
+        }
+        else
+            node.style.background = "transparent";
+
+        if (noFixedPosition)
+            this.fixIEPosition();
+
+        this.addController(
+            [$("fbMiniIcon", doc), "click", onMiniIconClick]
+        );
+
+        if (noFixedPosition)
+        {
+            this.addController(
+                [Firebug.browser.window, "scroll", this.fixIEPosition]
+            );
+        }
+
+        this.isInitialized = true;
+    },
+
+    shutdown: function()
+    {
+        var node = this.node;
+        node.style.height = Firebug.context.persistedState.height + "px";
+        node.style.width = "100%";
+        node.style.left = 0;
+        node.style.right = "";
+
+        if (this.node.nodeName.toLowerCase() == "iframe")
+        {
+            node.setAttribute("allowTransparency", "false");
+            this.document.body.style.backgroundColor = "#fff";
+        }
+        else
+            node.style.background = "#fff";
+
+        if (noFixedPosition)
+            this.fixIEPosition();
+
+        var doc = FirebugChrome.chromeMap.frame.document;
+
+        var mini = $("fbMiniChrome", doc);
+        mini.style.display = "none";
+
+        Controller.shutdown.apply(this);
+
+        this.isInitialized = false;
+    },
+
+    draw: function()
+    {
+
+    },
+
+    fixIEPosition: ChromeFrameBase.fixIEPosition
+
+});
+
+
+// ************************************************************************************************
+// ChromePopupBase
+
+/**
+ * @namespace
+ * @extends ns-chrome-ChromeBase
+ */
+var ChromePopupBase = extend(ChromeBase,
+/**@extend ns-chrome-ChromePopupBase*/
+{
+
+    initialize: function()
+    {
+        setClass(this.document.body, "FirebugPopup");
+
+        ChromeBase.initialize.call(this);
+
+        this.addController(
+            [Firebug.chrome.window, "resize", this.resize],
+            [Firebug.chrome.window, "unload", this.destroy]
+            //[Firebug.chrome.window, "beforeunload", this.destroy]
+        );
+
+        if (Env.Options.enablePersistent)
+        {
+            this.persist = bind(this.persist, this);
+            addEvent(Firebug.browser.window, "unload", this.persist);
+        }
+        else
+            this.addController(
+                [Firebug.browser.window, "unload", this.close]
+            );
+
+        fbVSplitter.onmousedown = onVSplitterMouseDown;
+    },
+
+    destroy: function()
+    {
+        var chromeWin = Firebug.chrome.window;
+        var left = chromeWin.screenX || chromeWin.screenLeft;
+        var top = chromeWin.screenY || chromeWin.screenTop;
+        var size = Firebug.chrome.getWindowSize();
+
+        Firebug.context.persistedState.popupTop = top;
+        Firebug.context.persistedState.popupLeft = left;
+        Firebug.context.persistedState.popupWidth = size.width;
+        Firebug.context.persistedState.popupHeight = size.height;
+
+        if (Firebug.saveCookies)
+            Firebug.savePrefs();
+
+        // TODO: xxxpedro sync detach reattach attach
+        var frame = FirebugChrome.chromeMap.frame;
+
+        if(frame)
+        {
+            dispatch(frame.panelMap, "detach", [this, frame]);
+
+            frame.reattach(this, frame);
+        }
+
+        if (Env.Options.enablePersistent)
+        {
+            removeEvent(Firebug.browser.window, "unload", this.persist);
+        }
+
+        ChromeBase.destroy.apply(this);
+
+        FirebugChrome.chromeMap.popup = null;
+
+        this.node.close();
+    },
+
+    persist: function()
+    {
+        persistTimeStart = new Date().getTime();
+
+        removeEvent(Firebug.browser.window, "unload", this.persist);
+
+        Firebug.Inspector.destroy();
+        Firebug.browser.window.FirebugOldBrowser = true;
+
+        var persistTimeStart = new Date().getTime();
+
+        var waitMainWindow = function()
+        {
+            var doc, head;
+
+            try
+            {
+                if (window.opener && !window.opener.FirebugOldBrowser && (doc = window.opener.document)/* &&
+                    doc.documentElement && (head = doc.documentElement.firstChild)*/)
+                {
+
+                    try
+                    {
+                        // exposes the FBL to the global namespace when in debug mode
+                        if (Env.isDebugMode)
+                        {
+                            window.FBL = FBL;
+                        }
+
+                        window.Firebug = Firebug;
+                        window.opener.Firebug = Firebug;
+
+                        Env.browser = window.opener;
+                        Firebug.browser = Firebug.context = new Context(Env.browser);
+                        Firebug.loadPrefs();
+
+                        registerConsole();
+
+                        // the delay time should be calculated right after registering the
+                        // console, once right after the console registration, call log messages
+                        // will be properly handled
+                        var persistDelay = new Date().getTime() - persistTimeStart;
+
+                        var chrome = Firebug.chrome;
+                        addEvent(Firebug.browser.window, "unload", chrome.persist);
+
+                        FBL.cacheDocument();
+                        Firebug.Inspector.create();
+
+                        Firebug.Console.logFormatted(
+                            ["Firebug could not capture console calls during " +
+                            persistDelay + "ms"],
+                            Firebug.context,
+                            "info"
+                        );
+
+                        setTimeout(function(){
+                            var htmlPanel = chrome.getPanel("HTML");
+                            htmlPanel.createUI();
+                        },50);
+
+                    }
+                    catch(pE)
+                    {
+                        alert("persist error: " + (pE.message || pE));
+                    }
+
+                }
+                else
+                {
+                    window.setTimeout(waitMainWindow, 0);
+                }
+
+            } catch (E) {
+                window.close();
+            }
+        };
+
+        waitMainWindow();
+    },
+
+    close: function()
+    {
+        this.destroy();
+    }
+
+});
+
+
+//************************************************************************************************
+// UI helpers
+
+var changeCommandLineVisibility = function changeCommandLineVisibility(visibility)
+{
+    var last = Firebug.chrome.commandLineVisible;
+    var visible = Firebug.chrome.commandLineVisible =
+        typeof visibility == "boolean" ? visibility : !Firebug.chrome.commandLineVisible;
+
+    if (visible != last)
+    {
+        if (visible)
+        {
+            fbBottom.className = "";
+
+            if (Firebug.CommandLine)
+                Firebug.CommandLine.activate();
+        }
+        else
+        {
+            if (Firebug.CommandLine)
+                Firebug.CommandLine.deactivate();
+
+            fbBottom.className = "hide";
+        }
+    }
+};
+
+var changeSidePanelVisibility = function changeSidePanelVisibility(visibility)
+{
+    var last = Firebug.chrome.sidePanelVisible;
+    Firebug.chrome.sidePanelVisible =
+        typeof visibility == "boolean" ? visibility : !Firebug.chrome.sidePanelVisible;
+
+    if (Firebug.chrome.sidePanelVisible != last)
+    {
+        fbPanelBox2.className = Firebug.chrome.sidePanelVisible ? "" : "hide";
+        fbPanelBar2Box.className = Firebug.chrome.sidePanelVisible ? "" : "hide";
+    }
+};
+
+
+// ************************************************************************************************
+// F12 Handler
+
+var onGlobalKeyDown = function onGlobalKeyDown(event)
+{
+    var keyCode = event.keyCode;
+    var shiftKey = event.shiftKey;
+    var ctrlKey = event.ctrlKey;
+
+    if (keyCode == 123 /* F12 */ && (!isFirefox && !shiftKey || shiftKey && isFirefox))
+    {
+        Firebug.chrome.toggle(false, ctrlKey);
+        cancelEvent(event, true);
+
+        // TODO: xxxpedro replace with a better solution. we're doing this
+        // to allow reactivating with the F12 key after being deactivated
+        if (Env.isChromeExtension)
+        {
+            Firebug.GoogleChrome.dispatch("FB_enableIcon");
+        }
+    }
+    else if (keyCode == 67 /* C */ && ctrlKey && shiftKey)
+    {
+        Firebug.Inspector.toggleInspect();
+        cancelEvent(event, true);
+    }
+    else if (keyCode == 76 /* L */ && ctrlKey && shiftKey)
+    {
+        Firebug.chrome.focusCommandLine();
+        cancelEvent(event, true);
+    }
+};
+
+var onMiniIconClick = function onMiniIconClick(event)
+{
+    Firebug.chrome.toggle(false, event.ctrlKey);
+    cancelEvent(event, true);
+};
+
+
+// ************************************************************************************************
+// Horizontal Splitter Handling
+
+var onHSplitterMouseDown = function onHSplitterMouseDown(event)
+{
+    addGlobalEvent("mousemove", onHSplitterMouseMove);
+    addGlobalEvent("mouseup", onHSplitterMouseUp);
+
+    if (isIE)
+        addEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp);
+
+    fbHSplitter.className = "fbOnMovingHSplitter";
+
+    return false;
+};
+
+var onHSplitterMouseMove = function onHSplitterMouseMove(event)
+{
+    cancelEvent(event, true);
+
+    var clientY = event.clientY;
+    var win = isIE
+        ? event.srcElement.ownerDocument.parentWindow
+        : event.target.defaultView || event.target.ownerDocument && event.target.ownerDocument.defaultView;
+
+    if (!win)
+        return;
+
+    if (win != win.parent)
+    {
+        var frameElement = win.frameElement;
+        if (frameElement)
+        {
+            var framePos = Firebug.browser.getElementPosition(frameElement).top;
+            clientY += framePos;
+
+            if (frameElement.style.position != "fixed")
+                clientY -= Firebug.browser.getWindowScrollPosition().top;
+        }
+    }
+
+    if (isOpera && isQuiksMode && win.frameElement.id == "FirebugUI")
+    {
+        clientY = Firebug.browser.getWindowSize().height - win.frameElement.offsetHeight + clientY;
+    }
+
+    /*
+    console.log(
+            typeof win.FBL != "undefined" ? "no-Chrome" : "Chrome",
+            //win.frameElement.id,
+            event.target,
+            clientY
+        );/**/
+
+    onHSplitterMouseMoveBuffer = clientY; // buffer
+
+    if (new Date().getTime() - lastHSplitterMouseMove > chromeRedrawSkipRate) // frame skipping
+    {
+        lastHSplitterMouseMove = new Date().getTime();
+        handleHSplitterMouseMove();
+    }
+    else
+        if (!onHSplitterMouseMoveTimer)
+            onHSplitterMouseMoveTimer = setTimeout(handleHSplitterMouseMove, chromeRedrawSkipRate);
+
+    // improving the resizing performance by canceling the mouse event.
+    // canceling events will prevent the page to receive such events, which would imply
+    // in more processing being expended.
+    cancelEvent(event, true);
+    return false;
+};
+
+var handleHSplitterMouseMove = function()
+{
+    if (onHSplitterMouseMoveTimer)
+    {
+        clearTimeout(onHSplitterMouseMoveTimer);
+        onHSplitterMouseMoveTimer = null;
+    }
+
+    var clientY = onHSplitterMouseMoveBuffer;
+
+    var windowSize = Firebug.browser.getWindowSize();
+    var scrollSize = Firebug.browser.getWindowScrollSize();
+
+    // compute chrome fixed size (top bar and command line)
+    var commandLineHeight = Firebug.chrome.commandLineVisible ? fbCommandLine.offsetHeight : 0;
+    var fixedHeight = topHeight + commandLineHeight;
+    var chromeNode = Firebug.chrome.node;
+
+    var scrollbarSize = !isIE && (scrollSize.width > windowSize.width) ? 17 : 0;
+
+    //var height = !isOpera ? chromeNode.offsetTop + chromeNode.clientHeight : windowSize.height;
+    var height =  windowSize.height;
+
+    // compute the min and max size of the chrome
+    var chromeHeight = Math.max(height - clientY + 5 - scrollbarSize, fixedHeight);
+        chromeHeight = Math.min(chromeHeight, windowSize.height - scrollbarSize);
+
+    Firebug.context.persistedState.height = chromeHeight;
+    chromeNode.style.height = chromeHeight + "px";
+
+    if (noFixedPosition)
+        Firebug.chrome.fixIEPosition();
+
+    Firebug.chrome.draw();
+};
+
+var onHSplitterMouseUp = function onHSplitterMouseUp(event)
+{
+    removeGlobalEvent("mousemove", onHSplitterMouseMove);
+    removeGlobalEvent("mouseup", onHSplitterMouseUp);
+
+    if (isIE)
+        removeEvent(Firebug.browser.document.documentElement, "mouseleave", onHSplitterMouseUp);
+
+    fbHSplitter.className = "";
+
+    Firebug.chrome.draw();
+
+    // avoid text selection in IE when returning to the document
+    // after the mouse leaves the document during the resizing
+    return false;
+};
+
+
+// ************************************************************************************************
+// Vertical Splitter Handling
+
+var onVSplitterMouseDown = function onVSplitterMouseDown(event)
+{
+    addGlobalEvent("mousemove", onVSplitterMouseMove);
+    addGlobalEvent("mouseup", onVSplitterMouseUp);
+
+    return false;
+};
+
+var onVSplitterMouseMove = function onVSplitterMouseMove(event)
+{
+    if (new Date().getTime() - lastVSplitterMouseMove > chromeRedrawSkipRate) // frame skipping
+    {
+        var target = event.target || event.srcElement;
+        if (target && target.ownerDocument) // avoid error when cursor reaches out of the chrome
+        {
+            var clientX = event.clientX;
+            var win = document.all
+                ? event.srcElement.ownerDocument.parentWindow
+                : event.target.ownerDocument.defaultView;
+
+            if (win != win.parent)
+                clientX += win.frameElement ? win.frameElement.offsetLeft : 0;
+
+            var size = Firebug.chrome.getSize();
+            var x = Math.max(size.width - clientX + 3, 6);
+
+            Firebug.context.persistedState.sidePanelWidth = x;
+            Firebug.chrome.draw();
+        }
+
+        lastVSplitterMouseMove = new Date().getTime();
+    }
+
+    cancelEvent(event, true);
+    return false;
+};
+
+var onVSplitterMouseUp = function onVSplitterMouseUp(event)
+{
+    removeGlobalEvent("mousemove", onVSplitterMouseMove);
+    removeGlobalEvent("mouseup", onVSplitterMouseUp);
+
+    Firebug.chrome.draw();
+};
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+Firebug.Lite =
+{
+};
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+Firebug.Lite.Cache =
+{
+    ID: "firebug-" + new Date().getTime()
+};
+
+// ************************************************************************************************
+
+/**
+ * TODO: if a cached element is cloned, the expando property will be cloned too in IE
+ * which will result in a bug. Firebug Lite will think the new cloned node is the old
+ * one.
+ *
+ * TODO: Investigate a possibility of cache validation, to be customized by each
+ * kind of cache. For ElementCache it should validate if the element still is
+ * inserted at the DOM.
+ */
+var cacheUID = 0;
+var createCache = function()
+{
+    var map = {};
+    var data = {};
+
+    var CID = Firebug.Lite.Cache.ID;
+
+    // better detection
+    var supportsDeleteExpando = !document.all;
+
+    var cacheFunction = function(element)
+    {
+        return cacheAPI.set(element);
+    };
+
+    var cacheAPI =
+    {
+        get: function(key)
+        {
+            return map.hasOwnProperty(key) ?
+                    map[key] :
+                    null;
+        },
+
+        set: function(element)
+        {
+            var id = getValidatedKey(element);
+
+            if (!id)
+            {
+                id = ++cacheUID;
+                element[CID] = id;
+            }
+
+            if (!map.hasOwnProperty(id))
+            {
+                map[id] = element;
+                data[id] = {};
+            }
+
+            return id;
+        },
+
+        unset: function(element)
+        {
+            var id = getValidatedKey(element);
+
+            if (!id) return;
+
+            if (supportsDeleteExpando)
+            {
+                delete element[CID];
+            }
+            else if (element.removeAttribute)
+            {
+                element.removeAttribute(CID);
+            }
+
+            delete map[id];
+            delete data[id];
+
+        },
+
+        key: function(element)
+        {
+            return getValidatedKey(element);
+        },
+
+        has: function(element)
+        {
+            var id = getValidatedKey(element);
+            return id && map.hasOwnProperty(id);
+        },
+
+        each: function(callback)
+        {
+            for (var key in map)
+            {
+                if (map.hasOwnProperty(key))
+                {
+                    callback(key, map[key]);
+                }
+            }
+        },
+
+        data: function(element, name, value)
+        {
+            // set data
+            if (value)
+            {
+                if (!name) return null;
+
+                var id = cacheAPI.set(element);
+
+                return data[id][name] = value;
+            }
+            // get data
+            else
+            {
+                var id = cacheAPI.key(element);
+
+                return data.hasOwnProperty(id) && data[id].hasOwnProperty(name) ?
+                        data[id][name] :
+                        null;
+            }
+        },
+
+        clear: function()
+        {
+            for (var id in map)
+            {
+                var element = map[id];
+                cacheAPI.unset(element);
+            }
+        }
+    };
+
+    var getValidatedKey = function(element)
+    {
+        var id = element[CID];
+
+        // If a cached element is cloned in IE, the expando property CID will be also
+        // cloned (differently than other browsers) resulting in a bug: Firebug Lite
+        // will think the new cloned node is the old one. To prevent this problem we're
+        // checking if the cached element matches the given element.
+        if (
+            !supportsDeleteExpando &&   // the problem happens when supportsDeleteExpando is false
+            id &&                       // the element has the expando property
+            map.hasOwnProperty(id) &&   // there is a cached element with the same id
+            map[id] != element          // but it is a different element than the current one
+            )
+        {
+            // remove the problematic property
+            element.removeAttribute(CID);
+
+            id = null;
+        }
+
+        return id;
+    };
+
+    FBL.append(cacheFunction, cacheAPI);
+
+    return cacheFunction;
+};
+
+// ************************************************************************************************
+
+// TODO: xxxpedro : check if we need really this on FBL scope
+Firebug.Lite.Cache.StyleSheet = createCache();
+Firebug.Lite.Cache.Element = createCache();
+
+// TODO: xxxpedro
+Firebug.Lite.Cache.Event = createCache();
+
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+var sourceMap = {};
+
+// ************************************************************************************************
+Firebug.Lite.Proxy =
+{
+    // jsonp callbacks
+    _callbacks: {},
+
+    /**
+     * Load a resource, either locally (directly) or externally (via proxy) using
+     * synchronous XHR calls. Loading external resources requires the proxy plugin to
+     * be installed and configured (see /plugin/proxy/proxy.php).
+     */
+    load: function(url)
+    {
+        var resourceDomain = getDomain(url);
+        var isLocalResource =
+            // empty domain means local URL
+            !resourceDomain ||
+            // same domain means local too
+            resourceDomain ==  Firebug.context.window.location.host; // TODO: xxxpedro context
+
+        return isLocalResource ? fetchResource(url) : fetchProxyResource(url);
+    },
+
+    /**
+     * Load a resource using JSONP technique.
+     */
+    loadJSONP: function(url, callback)
+    {
+        var script = createGlobalElement("script"),
+            doc = Firebug.context.document,
+
+            uid = "" + new Date().getTime(),
+            callbackName = "callback=Firebug.Lite.Proxy._callbacks." + uid,
+
+            jsonpURL = url.indexOf("?") != -1 ?
+                    url + "&" + callbackName :
+                    url + "?" + callbackName;
+
+        Firebug.Lite.Proxy._callbacks[uid] = function(data)
+        {
+            if (callback)
+                callback(data);
+
+            script.parentNode.removeChild(script);
+            delete Firebug.Lite.Proxy._callbacks[uid];
+        };
+
+        script.src = jsonpURL;
+
+        if (doc.documentElement)
+            doc.documentElement.appendChild(script);
+    },
+
+    /**
+     * Load a resource using YQL (not reliable).
+     */
+    YQL: function(url, callback)
+    {
+        var yql = "http://query.yahooapis.com/v1/public/yql?q=select%20*%20from%20html%20where%20url%3D%22" +
+                encodeURIComponent(url) + "%22&format=xml";
+
+        this.loadJSONP(yql, function(data)
+        {
+            var source = data.results[0];
+
+            // clean up YQL bogus elements
+            var match = /<body>\s+<p>([\s\S]+)<\/p>\s+<\/body>$/.exec(source);
+            if (match)
+                source = match[1];
+
+            console.log(source);
+        });
+    }
+};
+
+// ************************************************************************************************
+
+Firebug.Lite.Proxy.fetchResourceDisabledMessage =
+    "/* Firebug Lite resource fetching is disabled.\n" +
+    "To enabled it set the Firebug Lite option \"disableResourceFetching\" to \"false\".\n" +
+    "More info at http://getfirebug.com/firebuglite#Options */";
+
+var fetchResource = function(url)
+{
+    if (Firebug.disableResourceFetching)
+    {
+        var source = sourceMap[url] = Firebug.Lite.Proxy.fetchResourceDisabledMessage;
+        return source;
+    }
+
+    if (sourceMap.hasOwnProperty(url))
+        return sourceMap[url];
+
+    // Getting the native XHR object so our calls won't be logged in the Console Panel
+    var xhr = FBL.getNativeXHRObject();
+    xhr.open("get", url, false);
+    xhr.send();
+
+    var source = sourceMap[url] = xhr.responseText;
+    return source;
+};
+
+var fetchProxyResource = function(url)
+{
+    if (sourceMap.hasOwnProperty(url))
+        return sourceMap[url];
+
+    var proxyURL = Env.Location.baseDir + "plugin/proxy/proxy.php?url=" + encodeURIComponent(url);
+    var response = fetchResource(proxyURL);
+
+    try
+    {
+        var data = eval("(" + response + ")");
+    }
+    catch(E)
+    {
+        return "ERROR: Firebug Lite Proxy plugin returned an invalid response.";
+    }
+
+    var source = data ? data.contents : "";
+    return source;
+};
+
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+Firebug.Lite.Style =
+{
+};
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+Firebug.Lite.Script = function(window)
+{
+    this.fileName = null;
+    this.isValid = null;
+    this.baseLineNumber = null;
+    this.lineExtent = null;
+    this.tag = null;
+
+    this.functionName = null;
+    this.functionSource = null;
+};
+
+Firebug.Lite.Script.prototype =
+{
+    isLineExecutable: function(){},
+    pcToLine: function(){},
+    lineToPc: function(){},
+
+    toString: function()
+    {
+        return "Firebug.Lite.Script";
+    }
+};
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+
+Firebug.Lite.Browser = function(window)
+{
+    this.contentWindow = window;
+    this.contentDocument = window.document;
+    this.currentURI =
+    {
+        spec: window.location.href
+    };
+};
+
+Firebug.Lite.Browser.prototype =
+{
+    toString: function()
+    {
+        return "Firebug.Lite.Browser";
+    }
+};
+
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+/*
+    http://www.JSON.org/json2.js
+    2010-03-20
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 ? '0' + n : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date ?
+                    'Date(' + this[key] + ')' : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+*/
+
+/*jslint evil: true, strict: false */
+
+/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
+    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+// ************************************************************************************************
+
+var JSON = window.JSON || {};
+
+// ************************************************************************************************
+
+(function () {
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 ? '0' + n : n;
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function (key) {
+
+            return isFinite(this.valueOf()) ?
+                   this.getUTCFullYear()   + '-' +
+                 f(this.getUTCMonth() + 1) + '-' +
+                 f(this.getUTCDate())      + 'T' +
+                 f(this.getUTCHours())     + ':' +
+                 f(this.getUTCMinutes())   + ':' +
+                 f(this.getUTCSeconds())   + 'Z' : null;
+        };
+
+        String.prototype.toJSON =
+        Number.prototype.toJSON =
+        Boolean.prototype.toJSON = function (key) {
+            return this.valueOf();
+        };
+    }
+
+    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        gap,
+        indent,
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"' : '\\"',
+            '\\': '\\\\'
+        },
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        escapable.lastIndex = 0;
+        return escapable.test(string) ?
+            '"' + string.replace(escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string' ? c :
+                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' :
+            '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) ? String(value) : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0 ? '[]' :
+                    gap ? '[\n' + gap +
+                            partial.join(',\n' + gap) + '\n' +
+                                mind + ']' :
+                          '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    k = rep[i];
+                    if (typeof k === 'string') {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0 ? '{}' :
+                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
+                        mind + '}' : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                     typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            cx.lastIndex = 0;
+            if (cx.test(text)) {
+                text = text.replace(cx, function (a) {
+                    return '\\u' +
+                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (/^[\],:{}\s]*$/.
+test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
+replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
+replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function' ?
+                    walk({'': j}, '') : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+
+// ************************************************************************************************
+// registration
+
+FBL.JSON = JSON;
+
+// ************************************************************************************************
+}());
+
+/* See license.txt for terms of usage */
+
+(function(){
+// ************************************************************************************************
+
+/* Copyright (c) 2010-2011 Marcus Westin
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+var store = (function(){
+	var api = {},
+		win = window,
+		doc = win.document,
+		localStorageName = 'localStorage',
+		globalStorageName = 'globalStorage',
+		namespace = '__firebug__storejs__',
+		storage
+
+	api.disabled = false
+	api.set = function(key, value) {}
+	api.get = function(key) {}
+	api.remove = function(key) {}
+	api.clear = function() {}
+	api.transact = function(key, transactionFn) {
+		var val = api.get(key)
+		if (typeof val == 'undefined') { val = {} }
+		transactionFn(val)
+		api.set(key, val)
+	}
+
+	api.serialize = function(value) {
+		return JSON.stringify(value)
+	}
+	api.deserialize = function(value) {
+		if (typeof value != 'string') { return undefined }
+		return JSON.parse(value)
+	}
+
+	// Functions to encapsulate questionable FireFox 3.6.13 behavior
+	// when about.config::dom.storage.enabled === false
+	// See https://github.com/marcuswestin/store.js/issues#issue/13
+	function isLocalStorageNameSupported() {
+		try { return (localStorageName in win && win[localStorageName]) }
+		catch(err) { return false }
+	}
+
+	function isGlobalStorageNameSupported() {
+		try { return (globalStorageName in win && win[globalStorageName] && win[globalStorageName][win.location.hostname]) }
+		catch(err) { return false }
+	}
+
+	if (isLocalStorageNameSupported()) {
+		storage = win[localStorageName]
+		api.set = function(key, val) { storage.setItem(key, api.serialize(val)) }
+		api.get = function(key) { return api.deserialize(storage.getItem(key)) }
+		api.remove = function(key) { storage.removeItem(key) }
+		api.clear = function() { storage.clear() }
+
+	} else if (isGlobalStorageNameSupported()) {
+		storage = win[globalStorageName][win.location.hostname]
+		api.set = function(key, val) { storage[key] = api.serialize(val) }
+		api.get = function(key) { return api.deserialize(storage[key] && storage[key].value) }
+		api.remove = function(key) { delete storage[key] }
+		api.clear = function() { for (var key in storage ) { delete storage[key] } }
+
+	} else if (doc.documentElement.addBehavior) {
+		var storage = doc.createElement('div')
+		function withIEStorage(storeFunction) {
+			return function() {
+				var args = Array.prototype.slice.call(arguments, 0)
+				args.unshift(storage)
+				// See http://msdn.microsoft.com/en-us/library/ms531081(v=VS.85).aspx
+				// and http://msdn.microsoft.com/en-us/library/ms531424(v=VS.85).aspx
+				// TODO: xxxpedro doc.body is not always available so we must use doc.documentElement.
+				// We need to make sure this change won't affect the behavior of this library.
+				doc.documentElement.appendChild(storage)
+				storage.addBehavior('#default#userData')
+				storage.load(localStorageName)
+				var result = storeFunction.apply(api, args)
+				doc.documentElement.removeChild(storage)
+				return result
+			}
+		}
+		api.set = withIEStorage(function(storage, key, val) {
+			storage.setAttribute(key, api.serialize(val))
+			storage.save(localStorageName)
+		})
+		api.get = withIEStorage(function(storage, key) {
+			return api.deserialize(storage.getAttribute(key))
+		})
+		api.remove = withIEStorage(function(storage, key) {
+			storage.removeAttribute(key)
+			storage.save(localStorageName)
+		})
+		api.clear = withIEStorage(function(storage) {
+			var attributes = storage.XMLDocument.documentElement.attributes
+			storage.load(localStorageName)
+			for (var i=0, attr; attr = attributes[i]; i++) {
+				storage.removeAttribute(attr.name)
+			}
+			storage.save(localStorageName)
+		})
+	}
+
+	try {
+		api.set(namespace, namespace)
+		if (api.get(namespace) != namespace) { api.disabled = true }
+		api.remove(namespace)
+	} catch(e) {
+		api.disabled = true
+	}
+
+	return api
+})();
+
+if (typeof module != 'undefined') { module.exports = store }
+
+
+// ************************************************************************************************
+// registration
+
+FBL.Store = store;
+
+// ************************************************************************************************
+})();
+
+/* See license.txt for terms of usage */
+
+FBL.ns( /**@scope s_selector*/ function() { with (FBL) {
+// ************************************************************************************************
+
+/*
+ * Sizzle CSS Selector Engine - v1.0
+ *  Copyright 2009, The Dojo Foundation
+ *  Released under the MIT, BSD, and GPL Licenses.
+ *  More information: http://sizzlejs.com/
+ */
+
+var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
+    done = 0,
+    toString = Object.prototype.toString,
+    hasDuplicate = false,
+    baseHasDuplicate = true;
+
+// Here we check if the JavaScript engine is using some sort of
+// optimization where it does not always call our comparision
+// function. If that is the case, discard the hasDuplicate value.
+//   Thus far that includes Google Chrome.
+[0, 0].sort(function(){
+    baseHasDuplicate = false;
+    return 0;
+});
+
+/**
+ * @name Firebug.Selector
+ * @namespace
+ */
+
+/**
+ * @exports Sizzle as Firebug.Selector
+ */
+var Sizzle = function(selector, context, results, seed) {
+    results = results || [];
+    var origContext = context = context || document;
+
+    if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
+        return [];
+    }
+
+    if ( !selector || typeof selector !== "string" ) {
+        return results;
+    }
+
+    var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context),
+        soFar = selector;
+
+    // Reset the position of the chunker regexp (start from head)
+    while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) {
+        soFar = m[3];
+
+        parts.push( m[1] );
+
+        if ( m[2] ) {
+            extra = m[3];
+            break;
+        }
+    }
+
+    if ( parts.length > 1 && origPOS.exec( selector ) ) {
+        if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
+            set = posProcess( parts[0] + parts[1], context );
+        } else {
+            set = Expr.relative[ parts[0] ] ?
+                [ context ] :
+                Sizzle( parts.shift(), context );
+
+            while ( parts.length ) {
+                selector = parts.shift();
+
+                if ( Expr.relative[ selector ] )
+                    selector += parts.shift();
+
+                set = posProcess( selector, set );
+            }
+        }
+    } else {
+        // Take a shortcut and set the context if the root selector is an ID
+        // (but not if it'll be faster if the inner selector is an ID)
+        if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
+                Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
+            var ret = Sizzle.find( parts.shift(), context, contextXML );
+            context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
+        }
+
+        if ( context ) {
+            var ret = seed ?
+                { expr: parts.pop(), set: makeArray(seed) } :
+                Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
+            set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;
+
+            if ( parts.length > 0 ) {
+                checkSet = makeArray(set);
+            } else {
+                prune = false;
+            }
+
+            while ( parts.length ) {
+                var cur = parts.pop(), pop = cur;
+
+                if ( !Expr.relative[ cur ] ) {
+                    cur = "";
+                } else {
+                    pop = parts.pop();
+                }
+
+                if ( pop == null ) {
+                    pop = context;
+                }
+
+                Expr.relative[ cur ]( checkSet, pop, contextXML );
+            }
+        } else {
+            checkSet = parts = [];
+        }
+    }
+
+    if ( !checkSet ) {
+        checkSet = set;
+    }
+
+    if ( !checkSet ) {
+        throw "Syntax error, unrecognized expression: " + (cur || selector);
+    }
+
+    if ( toString.call(checkSet) === "[object Array]" ) {
+        if ( !prune ) {
+            results.push.apply( results, checkSet );
+        } else if ( context && context.nodeType === 1 ) {
+            for ( var i = 0; checkSet[i] != null; i++ ) {
+                if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
+                    results.push( set[i] );
+                }
+            }
+        } else {
+            for ( var i = 0; checkSet[i] != null; i++ ) {
+                if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
+                    results.push( set[i] );
+                }
+            }
+        }
+    } else {
+        makeArray( checkSet, results );
+    }
+
+    if ( extra ) {
+        Sizzle( extra, origContext, results, seed );
+        Sizzle.uniqueSort( results );
+    }
+
+    return results;
+};
+
+Sizzle.uniqueSort = function(results){
+    if ( sortOrder ) {
+        hasDuplicate = baseHasDuplicate;
+        results.sort(sortOrder);
+
+        if ( hasDuplicate ) {
+            for ( var i = 1; i < results.length; i++ ) {
+                if ( results[i] === results[i-1] ) {
+                    results.splice(i--, 1);
+                }
+            }
+        }
+    }
+
+    return results;
+};
+
+Sizzle.matches = function(expr, set){
+    return Sizzle(expr, null, null, set);
+};
+
+Sizzle.find = function(expr, context, isXML){
+    var set, match;
+
+    if ( !expr ) {
+        return [];
+    }
+
+    for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
+        var type = Expr.order[i], match;
+
+        if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
+            var left = match[1];
+            match.splice(1,1);
+
+            if ( left.substr( left.length - 1 ) !== "\\" ) {
+                match[1] = (match[1] || "").replace(/\\/g, "");
+                set = Expr.find[ type ]( match, context, isXML );
+                if ( set != null ) {
+                    expr = expr.replace( Expr.match[ type ], "" );
+                    break;
+                }
+            }
+        }
+    }
+
+    if ( !set ) {
+        set = context.getElementsByTagName("*");
+    }
+
+    return {set: set, expr: expr};
+};
+
+Sizzle.filter = function(expr, set, inplace, not){
+    var old = expr, result = [], curLoop = set, match, anyFound,
+        isXMLFilter = set && set[0] && isXML(set[0]);
+
+    while ( expr && set.length ) {
+        for ( var type in Expr.filter ) {
+            if ( (match = Expr.match[ type ].exec( expr )) != null ) {
+                var filter = Expr.filter[ type ], found, item;
+                anyFound = false;
+
+                if ( curLoop == result ) {
+                    result = [];
+                }
+
+                if ( Expr.preFilter[ type ] ) {
+                    match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );
+
+                    if ( !match ) {
+                        anyFound = found = true;
+                    } else if ( match === true ) {
+                        continue;
+                    }
+                }
+
+                if ( match ) {
+                    for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
+                        if ( item ) {
+                            found = filter( item, match, i, curLoop );
+                            var pass = not ^ !!found;
+
+                            if ( inplace && found != null ) {
+                                if ( pass ) {
+                                    anyFound = true;
+                                } else {
+                                    curLoop[i] = false;
+                                }
+                            } else if ( pass ) {
+                                result.push( item );
+                                anyFound = true;
+                            }
+                        }
+                    }
+                }
+
+                if ( found !== undefined ) {
+                    if ( !inplace ) {
+                        curLoop = result;
+                    }
+
+                    expr = expr.replace( Expr.match[ type ], "" );
+
+                    if ( !anyFound ) {
+                        return [];
+                    }
+
+                    break;
+                }
+            }
+        }
+
+        // Improper expression
+        if ( expr == old ) {
+            if ( anyFound == null ) {
+                throw "Syntax error, unrecognized expression: " + expr;
+            } else {
+                break;
+            }
+        }
+
+        old = expr;
+    }
+
+    return curLoop;
+};
+
+/**#@+ @ignore */
+var Expr = Sizzle.selectors = {
+    order: [ "ID", "NAME", "TAG" ],
+    match: {
+        ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+        CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/,
+        NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/,
+        ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
+        TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/,
+        CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
+        POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
+        PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
+    },
+    leftMatch: {},
+    attrMap: {
+        "class": "className",
+        "for": "htmlFor"
+    },
+    attrHandle: {
+        href: function(elem){
+            return elem.getAttribute("href");
+        }
+    },
+    relative: {
+        "+": function(checkSet, part, isXML){
+            var isPartStr = typeof part === "string",
+                isTag = isPartStr && !/\W/.test(part),
+                isPartStrNotTag = isPartStr && !isTag;
+
+            if ( isTag && !isXML ) {
+                part = part.toUpperCase();
+            }
+
+            for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
+                if ( (elem = checkSet[i]) ) {
+                    while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}
+
+                    checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
+                        elem || false :
+                        elem === part;
+                }
+            }
+
+            if ( isPartStrNotTag ) {
+                Sizzle.filter( part, checkSet, true );
+            }
+        },
+        ">": function(checkSet, part, isXML){
+            var isPartStr = typeof part === "string";
+
+            if ( isPartStr && !/\W/.test(part) ) {
+                part = isXML ? part : part.toUpperCase();
+
+                for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+                    var elem = checkSet[i];
+                    if ( elem ) {
+                        var parent = elem.parentNode;
+                        checkSet[i] = parent.nodeName === part ? parent : false;
+                    }
+                }
+            } else {
+                for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+                    var elem = checkSet[i];
+                    if ( elem ) {
+                        checkSet[i] = isPartStr ?
+                            elem.parentNode :
+                            elem.parentNode === part;
+                    }
+                }
+
+                if ( isPartStr ) {
+                    Sizzle.filter( part, checkSet, true );
+                }
+            }
+        },
+        "": function(checkSet, part, isXML){
+            var doneName = done++, checkFn = dirCheck;
+
+            if ( !/\W/.test(part) ) {
+                var nodeCheck = part = isXML ? part : part.toUpperCase();
+                checkFn = dirNodeCheck;
+            }
+
+            checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
+        },
+        "~": function(checkSet, part, isXML){
+            var doneName = done++, checkFn = dirCheck;
+
+            if ( typeof part === "string" && !/\W/.test(part) ) {
+                var nodeCheck = part = isXML ? part : part.toUpperCase();
+                checkFn = dirNodeCheck;
+            }
+
+            checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
+        }
+    },
+    find: {
+        ID: function(match, context, isXML){
+            if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                var m = context.getElementById(match[1]);
+                return m ? [m] : [];
+            }
+        },
+        NAME: function(match, context, isXML){
+            if ( typeof context.getElementsByName !== "undefined" ) {
+                var ret = [], results = context.getElementsByName(match[1]);
+
+                for ( var i = 0, l = results.length; i < l; i++ ) {
+                    if ( results[i].getAttribute("name") === match[1] ) {
+                        ret.push( results[i] );
+                    }
+                }
+
+                return ret.length === 0 ? null : ret;
+            }
+        },
+        TAG: function(match, context){
+            return context.getElementsByTagName(match[1]);
+        }
+    },
+    preFilter: {
+        CLASS: function(match, curLoop, inplace, result, not, isXML){
+            match = " " + match[1].replace(/\\/g, "") + " ";
+
+            if ( isXML ) {
+                return match;
+            }
+
+            for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
+                if ( elem ) {
+                    if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
+                        if ( !inplace )
+                            result.push( elem );
+                    } else if ( inplace ) {
+                        curLoop[i] = false;
+                    }
+                }
+            }
+
+            return false;
+        },
+        ID: function(match){
+            return match[1].replace(/\\/g, "");
+        },
+        TAG: function(match, curLoop){
+            for ( var i = 0; curLoop[i] === false; i++ ){}
+            return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
+        },
+        CHILD: function(match){
+            if ( match[1] == "nth" ) {
+                // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
+                var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
+                    match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
+                    !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);
+
+                // calculate the numbers (first)n+(last) including if they are negative
+                match[2] = (test[1] + (test[2] || 1)) - 0;
+                match[3] = test[3] - 0;
+            }
+
+            // TODO: Move to normal caching system
+            match[0] = done++;
+
+            return match;
+        },
+        ATTR: function(match, curLoop, inplace, result, not, isXML){
+            var name = match[1].replace(/\\/g, "");
+
+            if ( !isXML && Expr.attrMap[name] ) {
+                match[1] = Expr.attrMap[name];
+            }
+
+            if ( match[2] === "~=" ) {
+                match[4] = " " + match[4] + " ";
+            }
+
+            return match;
+        },
+        PSEUDO: function(match, curLoop, inplace, result, not){
+            if ( match[1] === "not" ) {
+                // If we're dealing with a complex expression, or a simple one
+                if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
+                    match[3] = Sizzle(match[3], null, null, curLoop);
+                } else {
+                    var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
+                    if ( !inplace ) {
+                        result.push.apply( result, ret );
+                    }
+                    return false;
+                }
+            } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
+                return true;
+            }
+
+            return match;
+        },
+        POS: function(match){
+            match.unshift( true );
+            return match;
+        }
+    },
+    filters: {
+        enabled: function(elem){
+            return elem.disabled === false && elem.type !== "hidden";
+        },
+        disabled: function(elem){
+            return elem.disabled === true;
+        },
+        checked: function(elem){
+            return elem.checked === true;
+        },
+        selected: function(elem){
+            // Accessing this property makes selected-by-default
+            // options in Safari work properly
+            elem.parentNode.selectedIndex;
+            return elem.selected === true;
+        },
+        parent: function(elem){
+            return !!elem.firstChild;
+        },
+        empty: function(elem){
+            return !elem.firstChild;
+        },
+        has: function(elem, i, match){
+            return !!Sizzle( match[3], elem ).length;
+        },
+        header: function(elem){
+            return /h\d/i.test( elem.nodeName );
+        },
+        text: function(elem){
+            return "text" === elem.type;
+        },
+        radio: function(elem){
+            return "radio" === elem.type;
+        },
+        checkbox: function(elem){
+            return "checkbox" === elem.type;
+        },
+        file: function(elem){
+            return "file" === elem.type;
+        },
+        password: function(elem){
+            return "password" === elem.type;
+        },
+        submit: function(elem){
+            return "submit" === elem.type;
+        },
+        image: function(elem){
+            return "image" === elem.type;
+        },
+        reset: function(elem){
+            return "reset" === elem.type;
+        },
+        button: function(elem){
+            return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
+        },
+        input: function(elem){
+            return /input|select|textarea|button/i.test(elem.nodeName);
+        }
+    },
+    setFilters: {
+        first: function(elem, i){
+            return i === 0;
+        },
+        last: function(elem, i, match, array){
+            return i === array.length - 1;
+        },
+        even: function(elem, i){
+            return i % 2 === 0;
+        },
+        odd: function(elem, i){
+            return i % 2 === 1;
+        },
+        lt: function(elem, i, match){
+            return i < match[3] - 0;
+        },
+        gt: function(elem, i, match){
+            return i > match[3] - 0;
+        },
+        nth: function(elem, i, match){
+            return match[3] - 0 == i;
+        },
+        eq: function(elem, i, match){
+            return match[3] - 0 == i;
+        }
+    },
+    filter: {
+        PSEUDO: function(elem, match, i, array){
+            var name = match[1], filter = Expr.filters[ name ];
+
+            if ( filter ) {
+                return filter( elem, i, match, array );
+            } else if ( name === "contains" ) {
+                return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
+            } else if ( name === "not" ) {
+                var not = match[3];
+
+                for ( var i = 0, l = not.length; i < l; i++ ) {
+                    if ( not[i] === elem ) {
+                        return false;
+                    }
+                }
+
+                return true;
+            }
+        },
+        CHILD: function(elem, match){
+            var type = match[1], node = elem;
+            switch (type) {
+                case 'only':
+                case 'first':
+                    while ( (node = node.previousSibling) )  {
+                        if ( node.nodeType === 1 ) return false;
+                    }
+                    if ( type == 'first') return true;
+                    node = elem;
+                case 'last':
+                    while ( (node = node.nextSibling) )  {
+                        if ( node.nodeType === 1 ) return false;
+                    }
+                    return true;
+                case 'nth':
+                    var first = match[2], last = match[3];
+
+                    if ( first == 1 && last == 0 ) {
+                        return true;
+                    }
+
+                    var doneName = match[0],
+                        parent = elem.parentNode;
+
+                    if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
+                        var count = 0;
+                        for ( node = parent.firstChild; node; node = node.nextSibling ) {
+                            if ( node.nodeType === 1 ) {
+                                node.nodeIndex = ++count;
+                            }
+                        }
+                        parent.sizcache = doneName;
+                    }
+
+                    var diff = elem.nodeIndex - last;
+                    if ( first == 0 ) {
+                        return diff == 0;
+                    } else {
+                        return ( diff % first == 0 && diff / first >= 0 );
+                    }
+            }
+        },
+        ID: function(elem, match){
+            return elem.nodeType === 1 && elem.getAttribute("id") === match;
+        },
+        TAG: function(elem, match){
+            return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
+        },
+        CLASS: function(elem, match){
+            return (" " + (elem.className || elem.getAttribute("class")) + " ")
+                .indexOf( match ) > -1;
+        },
+        ATTR: function(elem, match){
+            var name = match[1],
+                result = Expr.attrHandle[ name ] ?
+                    Expr.attrHandle[ name ]( elem ) :
+                    elem[ name ] != null ?
+                        elem[ name ] :
+                        elem.getAttribute( name ),
+                value = result + "",
+                type = match[2],
+                check = match[4];
+
+            return result == null ?
+                type === "!=" :
+                type === "=" ?
+                value === check :
+                type === "*=" ?
+                value.indexOf(check) >= 0 :
+                type === "~=" ?
+                (" " + value + " ").indexOf(check) >= 0 :
+                !check ?
+                value && result !== false :
+                type === "!=" ?
+                value != check :
+                type === "^=" ?
+                value.indexOf(check) === 0 :
+                type === "$=" ?
+                value.substr(value.length - check.length) === check :
+                type === "|=" ?
+                value === check || value.substr(0, check.length + 1) === check + "-" :
+                false;
+        },
+        POS: function(elem, match, i, array){
+            var name = match[2], filter = Expr.setFilters[ name ];
+
+            if ( filter ) {
+                return filter( elem, i, match, array );
+            }
+        }
+    }
+};
+
+var origPOS = Expr.match.POS;
+
+for ( var type in Expr.match ) {
+    Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
+    Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source );
+}
+
+var makeArray = function(array, results) {
+    array = Array.prototype.slice.call( array, 0 );
+
+    if ( results ) {
+        results.push.apply( results, array );
+        return results;
+    }
+
+    return array;
+};
+
+// Perform a simple check to determine if the browser is capable of
+// converting a NodeList to an array using builtin methods.
+try {
+    Array.prototype.slice.call( document.documentElement.childNodes, 0 );
+
+// Provide a fallback method if it does not work
+} catch(e){
+    makeArray = function(array, results) {
+        var ret = results || [];
+
+        if ( toString.call(array) === "[object Array]" ) {
+            Array.prototype.push.apply( ret, array );
+        } else {
+            if ( typeof array.length === "number" ) {
+                for ( var i = 0, l = array.length; i < l; i++ ) {
+                    ret.push( array[i] );
+                }
+            } else {
+                for ( var i = 0; array[i]; i++ ) {
+                    ret.push( array[i] );
+                }
+            }
+        }
+
+        return ret;
+    };
+}
+
+var sortOrder;
+
+if ( document.documentElement.compareDocumentPosition ) {
+    sortOrder = function( a, b ) {
+        if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
+            if ( a == b ) {
+                hasDuplicate = true;
+            }
+            return 0;
+        }
+
+        var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
+        if ( ret === 0 ) {
+            hasDuplicate = true;
+        }
+        return ret;
+    };
+} else if ( "sourceIndex" in document.documentElement ) {
+    sortOrder = function( a, b ) {
+        if ( !a.sourceIndex || !b.sourceIndex ) {
+            if ( a == b ) {
+                hasDuplicate = true;
+            }
+            return 0;
+        }
+
+        var ret = a.sourceIndex - b.sourceIndex;
+        if ( ret === 0 ) {
+            hasDuplicate = true;
+        }
+        return ret;
+    };
+} else if ( document.createRange ) {
+    sortOrder = function( a, b ) {
+        if ( !a.ownerDocument || !b.ownerDocument ) {
+            if ( a == b ) {
+                hasDuplicate = true;
+            }
+            return 0;
+        }
+
+        var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
+        aRange.setStart(a, 0);
+        aRange.setEnd(a, 0);
+        bRange.setStart(b, 0);
+        bRange.setEnd(b, 0);
+        var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
+        if ( ret === 0 ) {
+            hasDuplicate = true;
+        }
+        return ret;
+    };
+}
+
+// Check to see if the browser returns elements by name when
+// querying by getElementById (and provide a workaround)
+(function(){
+    // We're going to inject a fake input element with a specified name
+    var form = document.createElement("div"),
+        id = "script" + (new Date).getTime();
+    form.innerHTML = "<a name='" + id + "'/>";
+
+    // Inject it into the root element, check its status, and remove it quickly
+    var root = document.documentElement;
+    root.insertBefore( form, root.firstChild );
+
+    // The workaround has to do additional checks after a getElementById
+    // Which slows things down for other browsers (hence the branching)
+    if ( !!document.getElementById( id ) ) {
+        Expr.find.ID = function(match, context, isXML){
+            if ( typeof context.getElementById !== "undefined" && !isXML ) {
+                var m = context.getElementById(match[1]);
+                return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
+            }
+        };
+
+        Expr.filter.ID = function(elem, match){
+            var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
+            return elem.nodeType === 1 && node && node.nodeValue === match;
+        };
+    }
+
+    root.removeChild( form );
+    root = form = null; // release memory in IE
+})();
+
+(function(){
+    // Check to see if the browser returns only elements
+    // when doing getElementsByTagName("*")
+
+    // Create a fake element
+    var div = document.createElement("div");
+    div.appendChild( document.createComment("") );
+
+    // Make sure no comments are found
+    if ( div.getElementsByTagName("*").length > 0 ) {
+        Expr.find.TAG = function(match, context){
+            var results = context.getElementsByTagName(match[1]);
+
+            // Filter out possible comments
+            if ( match[1] === "*" ) {
+                var tmp = [];
+
+                for ( var i = 0; results[i]; i++ ) {
+                    if ( results[i].nodeType === 1 ) {
+                        tmp.push( results[i] );
+                    }
+                }
+
+                results = tmp;
+            }
+
+            return results;
+        };
+    }
+
+    // Check to see if an attribute returns normalized href attributes
+    div.innerHTML = "<a href='#'></a>";
+    if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
+            div.firstChild.getAttribute("href") !== "#" ) {
+        Expr.attrHandle.href = function(elem){
+            return elem.getAttribute("href", 2);
+        };
+    }
+
+    div = null; // release memory in IE
+})();
+
+if ( document.querySelectorAll ) (function(){
+    var oldSizzle = Sizzle, div = document.createElement("div");
+    div.innerHTML = "<p class='TEST'></p>";
+
+    // Safari can't handle uppercase or unicode characters when
+    // in quirks mode.
+    if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
+        return;
+    }
+
+    Sizzle = function(query, context, extra, seed){
+        context = context || document;
+
+        // Only use querySelectorAll on non-XML documents
+        // (ID selectors don't work in non-HTML documents)
+        if ( !seed && context.nodeType === 9 && !isXML(context) ) {
+            try {
+                return makeArray( context.querySelectorAll(query), extra );
+            } catch(e){}
+        }
+
+        return oldSizzle(query, context, extra, seed);
+    };
+
+    for ( var prop in oldSizzle ) {
+        Sizzle[ prop ] = oldSizzle[ prop ];
+    }
+
+    div = null; // release memory in IE
+})();
+
+if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
+    var div = document.createElement("div");
+    div.innerHTML = "<div class='test e'></div><div class='test'></div>";
+
+    // Opera can't find a second classname (in 9.6)
+    if ( div.getElementsByClassName("e").length === 0 )
+        return;
+
+    // Safari caches class attributes, doesn't catch changes (in 3.2)
+    div.lastChild.className = "e";
+
+    if ( div.getElementsByClassName("e").length === 1 )
+        return;
+
+    Expr.order.splice(1, 0, "CLASS");
+    Expr.find.CLASS = function(match, context, isXML) {
+        if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
+            return context.getElementsByClassName(match[1]);
+        }
+    };
+
+    div = null; // release memory in IE
+})();
+
+function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+    var sibDir = dir == "previousSibling" && !isXML;
+    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+        var elem = checkSet[i];
+        if ( elem ) {
+            if ( sibDir && elem.nodeType === 1 ){
+                elem.sizcache = doneName;
+                elem.sizset = i;
+            }
+            elem = elem[dir];
+            var match = false;
+
+            while ( elem ) {
+                if ( elem.sizcache === doneName ) {
+                    match = checkSet[elem.sizset];
+                    break;
+                }
+
+                if ( elem.nodeType === 1 && !isXML ){
+                    elem.sizcache = doneName;
+                    elem.sizset = i;
+                }
+
+                if ( elem.nodeName === cur ) {
+                    match = elem;
+                    break;
+                }
+
+                elem = elem[dir];
+            }
+
+            checkSet[i] = match;
+        }
+    }
+}
+
+function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
+    var sibDir = dir == "previousSibling" && !isXML;
+    for ( var i = 0, l = checkSet.length; i < l; i++ ) {
+        var elem = checkSet[i];
+        if ( elem ) {
+            if ( sibDir && elem.nodeType === 1 ) {
+                elem.sizcache = doneName;
+                elem.sizset = i;
+            }
+            elem = elem[dir];
+            var match = false;
+
+            while ( elem ) {
+                if ( elem.sizcache === doneName ) {
+                    match = checkSet[elem.sizset];
+                    break;
+                }
+
+                if ( elem.nodeType === 1 ) {
+                    if ( !isXML ) {
+                        elem.sizcache = doneName;
+                        elem.sizset = i;
+                    }
+                    if ( typeof cur !== "string" ) {
+                        if ( elem === cur ) {
+                            match = true;
+                            break;
+                        }
+
+                    } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
+                        match = elem;
+                        break;
+                    }
+                }
+
+                elem = elem[dir];
+            }
+
+            checkSet[i] = match;
+        }
+    }
+}
+
+var contains = document.compareDocumentPosition ?  function(a, b){
+    return a.compareDocumentPosition(b) & 16;
+} : function(a, b){
+    return a !== b && (a.contains ? a.contains(b) : true);
+};
+
+var isXML = function(elem){
+    return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
+        !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML";
+};
+
+var posProcess = function(selector, context){
+    var tmpSet = [], later = "", match,
+        root = context.nodeType ? [context] : context;
+
+    // Position selectors must be done after the filter
+    // And so must :not(positional) so we move all PSEUDOs to the end
+    while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
+        later += match[0];
+        selector = selector.replace( Expr.match.PSEUDO, "" );
+    }
+
+    selector = Expr.relative[selector] ? selector + "*" : selector;
+
+    for ( var i = 0, l = root.length; i < l; i++ ) {
+        Sizzle( selector, root[i], tmpSet );
+    }
+
+    return Sizzle.filter( later, tmpSet );
+};
+
+// EXPOSE
+
+Firebug.Selector = Sizzle;
+
+/**#@-*/
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Inspector Module
+
+var ElementCache = Firebug.Lite.Cache.Element;
+
+var inspectorTS, inspectorTimer, isInspecting;
+
+Firebug.Inspector =
+{
+    create: function()
+    {
+        offlineFragment = Env.browser.document.createDocumentFragment();
+
+        createBoxModelInspector();
+        createOutlineInspector();
+    },
+
+    destroy: function()
+    {
+        destroyBoxModelInspector();
+        destroyOutlineInspector();
+
+        offlineFragment = null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Inspect functions
+
+    toggleInspect: function()
+    {
+        if (isInspecting)
+        {
+            this.stopInspecting();
+        }
+        else
+        {
+            Firebug.chrome.inspectButton.changeState("pressed");
+            this.startInspecting();
+        }
+    },
+
+    startInspecting: function()
+    {
+        isInspecting = true;
+
+        Firebug.chrome.selectPanel("HTML");
+
+        createInspectorFrame();
+
+        var size = Firebug.browser.getWindowScrollSize();
+
+        fbInspectFrame.style.width = size.width + "px";
+        fbInspectFrame.style.height = size.height + "px";
+
+        //addEvent(Firebug.browser.document.documentElement, "mousemove", Firebug.Inspector.onInspectingBody);
+
+        addEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting);
+        addEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick);
+    },
+
+    stopInspecting: function()
+    {
+        isInspecting = false;
+
+        if (outlineVisible) this.hideOutline();
+        removeEvent(fbInspectFrame, "mousemove", Firebug.Inspector.onInspecting);
+        removeEvent(fbInspectFrame, "mousedown", Firebug.Inspector.onInspectingClick);
+
+        destroyInspectorFrame();
+
+        Firebug.chrome.inspectButton.restore();
+
+        if (Firebug.chrome.type == "popup")
+            Firebug.chrome.node.focus();
+    },
+
+    onInspectingClick: function(e)
+    {
+        fbInspectFrame.style.display = "none";
+        var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY);
+        fbInspectFrame.style.display = "block";
+
+        // Avoid inspecting the outline, and the FirebugUI
+        var id = targ.id;
+        if (id && /^fbOutline\w$/.test(id)) return;
+        if (id == "FirebugUI") return;
+
+        // Avoid looking at text nodes in Opera
+        while (targ.nodeType != 1) targ = targ.parentNode;
+
+        //Firebug.Console.log(targ);
+        Firebug.Inspector.stopInspecting();
+    },
+
+    onInspecting: function(e)
+    {
+        if (new Date().getTime() - lastInspecting > 30)
+        {
+            fbInspectFrame.style.display = "none";
+            var targ = Firebug.browser.getElementFromPoint(e.clientX, e.clientY);
+            fbInspectFrame.style.display = "block";
+
+            // Avoid inspecting the outline, and the FirebugUI
+            var id = targ.id;
+            if (id && /^fbOutline\w$/.test(id)) return;
+            if (id == "FirebugUI") return;
+
+            // Avoid looking at text nodes in Opera
+            while (targ.nodeType != 1) targ = targ.parentNode;
+
+            if (targ.nodeName.toLowerCase() == "body") return;
+
+            //Firebug.Console.log(e.clientX, e.clientY, targ);
+            Firebug.Inspector.drawOutline(targ);
+
+            if (ElementCache(targ))
+            {
+                var target = ""+ElementCache.key(targ);
+                var lazySelect = function()
+                {
+                    inspectorTS = new Date().getTime();
+
+                    if (Firebug.HTML)
+                        Firebug.HTML.selectTreeNode(""+ElementCache.key(targ));
+                };
+
+                if (inspectorTimer)
+                {
+                    clearTimeout(inspectorTimer);
+                    inspectorTimer = null;
+                }
+
+                if (new Date().getTime() - inspectorTS > 200)
+                    setTimeout(lazySelect, 0);
+                else
+                    inspectorTimer = setTimeout(lazySelect, 300);
+            }
+
+            lastInspecting = new Date().getTime();
+        }
+    },
+
+    // TODO: xxxpedro remove this?
+    onInspectingBody: function(e)
+    {
+        if (new Date().getTime() - lastInspecting > 30)
+        {
+            var targ = e.target;
+
+            // Avoid inspecting the outline, and the FirebugUI
+            var id = targ.id;
+            if (id && /^fbOutline\w$/.test(id)) return;
+            if (id == "FirebugUI") return;
+
+            // Avoid looking at text nodes in Opera
+            while (targ.nodeType != 1) targ = targ.parentNode;
+
+            if (targ.nodeName.toLowerCase() == "body") return;
+
+            //Firebug.Console.log(e.clientX, e.clientY, targ);
+            Firebug.Inspector.drawOutline(targ);
+
+            if (ElementCache.has(targ))
+                FBL.Firebug.HTML.selectTreeNode(""+ElementCache.key(targ));
+
+            lastInspecting = new Date().getTime();
+        }
+    },
+
+    /**
+     *
+     *   llttttttrr
+     *   llttttttrr
+     *   ll      rr
+     *   ll      rr
+     *   llbbbbbbrr
+     *   llbbbbbbrr
+     */
+    drawOutline: function(el)
+    {
+        var border = 2;
+        var scrollbarSize = 17;
+
+        var windowSize = Firebug.browser.getWindowSize();
+        var scrollSize = Firebug.browser.getWindowScrollSize();
+        var scrollPosition = Firebug.browser.getWindowScrollPosition();
+
+        var box = Firebug.browser.getElementBox(el);
+
+        var top = box.top;
+        var left = box.left;
+        var height = box.height;
+        var width = box.width;
+
+        var freeHorizontalSpace = scrollPosition.left + windowSize.width - left - width -
+                (!isIE && scrollSize.height > windowSize.height ? // is *vertical* scrollbar visible
+                 scrollbarSize : 0);
+
+        var freeVerticalSpace = scrollPosition.top + windowSize.height - top - height -
+                (!isIE && scrollSize.width > windowSize.width ? // is *horizontal* scrollbar visible
+                scrollbarSize : 0);
+
+        var numVerticalBorders = freeVerticalSpace > 0 ? 2 : 1;
+
+        var o = outlineElements;
+        var style;
+
+        style = o.fbOutlineT.style;
+        style.top = top-border + "px";
+        style.left = left + "px";
+        style.height = border + "px";  // TODO: on initialize()
+        style.width = width + "px";
+
+        style = o.fbOutlineL.style;
+        style.top = top-border + "px";
+        style.left = left-border + "px";
+        style.height = height+ numVerticalBorders*border + "px";
+        style.width = border + "px";  // TODO: on initialize()
+
+        style = o.fbOutlineB.style;
+        if (freeVerticalSpace > 0)
+        {
+            style.top = top+height + "px";
+            style.left = left + "px";
+            style.width = width + "px";
+            //style.height = border + "px"; // TODO: on initialize() or worst case?
+        }
+        else
+        {
+            style.top = -2*border + "px";
+            style.left = -2*border + "px";
+            style.width = border + "px";
+            //style.height = border + "px";
+        }
+
+        style = o.fbOutlineR.style;
+        if (freeHorizontalSpace > 0)
+        {
+            style.top = top-border + "px";
+            style.left = left+width + "px";
+            style.height = height + numVerticalBorders*border + "px";
+            style.width = (freeHorizontalSpace < border ? freeHorizontalSpace : border) + "px";
+        }
+        else
+        {
+            style.top = -2*border + "px";
+            style.left = -2*border + "px";
+            style.height = border + "px";
+            style.width = border + "px";
+        }
+
+        if (!outlineVisible) this.showOutline();
+    },
+
+    hideOutline: function()
+    {
+        if (!outlineVisible) return;
+
+        for (var name in outline)
+            offlineFragment.appendChild(outlineElements[name]);
+
+        outlineVisible = false;
+    },
+
+    showOutline: function()
+    {
+        if (outlineVisible) return;
+
+        if (boxModelVisible) this.hideBoxModel();
+
+        for (var name in outline)
+            Firebug.browser.document.getElementsByTagName("body")[0].appendChild(outlineElements[name]);
+
+        outlineVisible = true;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Box Model
+
+    drawBoxModel: function(el)
+    {
+        // avoid error when the element is not attached a document
+        if (!el || !el.parentNode)
+            return;
+
+        var box = Firebug.browser.getElementBox(el);
+
+        var windowSize = Firebug.browser.getWindowSize();
+        var scrollPosition = Firebug.browser.getWindowScrollPosition();
+
+        // element may be occluded by the chrome, when in frame mode
+        var offsetHeight = Firebug.chrome.type == "frame" ? Firebug.context.persistedState.height : 0;
+
+        // if element box is not inside the viewport, don't draw the box model
+        if (box.top > scrollPosition.top + windowSize.height - offsetHeight ||
+            box.left > scrollPosition.left + windowSize.width ||
+            scrollPosition.top > box.top + box.height ||
+            scrollPosition.left > box.left + box.width )
+            return;
+
+        var top = box.top;
+        var left = box.left;
+        var height = box.height;
+        var width = box.width;
+
+        var margin = Firebug.browser.getMeasurementBox(el, "margin");
+        var padding = Firebug.browser.getMeasurementBox(el, "padding");
+        var border = Firebug.browser.getMeasurementBox(el, "border");
+
+        boxModelStyle.top = top - margin.top + "px";
+        boxModelStyle.left = left - margin.left + "px";
+        boxModelStyle.height = height + margin.top + margin.bottom + "px";
+        boxModelStyle.width = width + margin.left + margin.right + "px";
+
+        boxBorderStyle.top = margin.top + "px";
+        boxBorderStyle.left = margin.left + "px";
+        boxBorderStyle.height = height + "px";
+        boxBorderStyle.width = width + "px";
+
+        boxPaddingStyle.top = margin.top + border.top + "px";
+        boxPaddingStyle.left = margin.left + border.left + "px";
+        boxPaddingStyle.height = height - border.top - border.bottom + "px";
+        boxPaddingStyle.width = width - border.left - border.right + "px";
+
+        boxContentStyle.top = margin.top + border.top + padding.top + "px";
+        boxContentStyle.left = margin.left + border.left + padding.left + "px";
+        boxContentStyle.height = height - border.top - padding.top - padding.bottom - border.bottom + "px";
+        boxContentStyle.width = width - border.left - padding.left - padding.right - border.right + "px";
+
+        if (!boxModelVisible) this.showBoxModel();
+    },
+
+    hideBoxModel: function()
+    {
+        if (!boxModelVisible) return;
+
+        offlineFragment.appendChild(boxModel);
+        boxModelVisible = false;
+    },
+
+    showBoxModel: function()
+    {
+        if (boxModelVisible) return;
+
+        if (outlineVisible) this.hideOutline();
+
+        Firebug.browser.document.getElementsByTagName("body")[0].appendChild(boxModel);
+        boxModelVisible = true;
+    }
+
+};
+
+// ************************************************************************************************
+// Inspector Internals
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Shared variables
+
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// Internal variables
+
+var offlineFragment = null;
+
+var boxModelVisible = false;
+
+var boxModel, boxModelStyle,
+    boxMargin, boxMarginStyle,
+    boxBorder, boxBorderStyle,
+    boxPadding, boxPaddingStyle,
+    boxContent, boxContentStyle;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var resetStyle = "margin:0; padding:0; border:0; position:absolute; overflow:hidden; display:block;";
+var offscreenStyle = resetStyle + "top:-1234px; left:-1234px;";
+
+var inspectStyle = resetStyle + "z-index: 2147483500;";
+var inspectFrameStyle = resetStyle + "z-index: 2147483550; top:0; left:0; background:url(" +
+                        Env.Location.skinDir + "pixel_transparent.gif);";
+
+//if (Env.Options.enableTrace) inspectFrameStyle = resetStyle + "z-index: 2147483550; top: 0; left: 0; background: #ff0; opacity: 0.05; _filter: alpha(opacity=5);";
+
+var inspectModelOpacity = isIE ? "filter:alpha(opacity=80);" : "opacity:0.8;";
+var inspectModelStyle = inspectStyle + inspectModelOpacity;
+var inspectMarginStyle = inspectStyle + "background: #EDFF64; height:100%; width:100%;";
+var inspectBorderStyle = inspectStyle + "background: #666;";
+var inspectPaddingStyle = inspectStyle + "background: SlateBlue;";
+var inspectContentStyle = inspectStyle + "background: SkyBlue;";
+
+
+var outlineStyle = {
+    fbHorizontalLine: "background: #3875D7;height: 2px;",
+    fbVerticalLine: "background: #3875D7;width: 2px;"
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var lastInspecting = 0;
+var fbInspectFrame = null;
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var outlineVisible = false;
+var outlineElements = {};
+var outline = {
+  "fbOutlineT": "fbHorizontalLine",
+  "fbOutlineL": "fbVerticalLine",
+  "fbOutlineB": "fbHorizontalLine",
+  "fbOutlineR": "fbVerticalLine"
+};
+
+
+var getInspectingTarget = function()
+{
+
+};
+
+// ************************************************************************************************
+// Section
+
+var createInspectorFrame = function createInspectorFrame()
+{
+    fbInspectFrame = createGlobalElement("div");
+    fbInspectFrame.id = "fbInspectFrame";
+    fbInspectFrame.firebugIgnore = true;
+    fbInspectFrame.style.cssText = inspectFrameStyle;
+    Firebug.browser.document.getElementsByTagName("body")[0].appendChild(fbInspectFrame);
+};
+
+var destroyInspectorFrame = function destroyInspectorFrame()
+{
+    if (fbInspectFrame)
+    {
+        Firebug.browser.document.getElementsByTagName("body")[0].removeChild(fbInspectFrame);
+        fbInspectFrame = null;
+    }
+};
+
+var createOutlineInspector = function createOutlineInspector()
+{
+    for (var name in outline)
+    {
+        var el = outlineElements[name] = createGlobalElement("div");
+        el.id = name;
+        el.firebugIgnore = true;
+        el.style.cssText = inspectStyle + outlineStyle[outline[name]];
+        offlineFragment.appendChild(el);
+    }
+};
+
+var destroyOutlineInspector = function destroyOutlineInspector()
+{
+    for (var name in outline)
+    {
+        var el = outlineElements[name];
+        el.parentNode.removeChild(el);
+    }
+};
+
+var createBoxModelInspector = function createBoxModelInspector()
+{
+    boxModel = createGlobalElement("div");
+    boxModel.id = "fbBoxModel";
+    boxModel.firebugIgnore = true;
+    boxModelStyle = boxModel.style;
+    boxModelStyle.cssText = inspectModelStyle;
+
+    boxMargin = createGlobalElement("div");
+    boxMargin.id = "fbBoxMargin";
+    boxMarginStyle = boxMargin.style;
+    boxMarginStyle.cssText = inspectMarginStyle;
+    boxModel.appendChild(boxMargin);
+
+    boxBorder = createGlobalElement("div");
+    boxBorder.id = "fbBoxBorder";
+    boxBorderStyle = boxBorder.style;
+    boxBorderStyle.cssText = inspectBorderStyle;
+    boxModel.appendChild(boxBorder);
+
+    boxPadding = createGlobalElement("div");
+    boxPadding.id = "fbBoxPadding";
+    boxPaddingStyle = boxPadding.style;
+    boxPaddingStyle.cssText = inspectPaddingStyle;
+    boxModel.appendChild(boxPadding);
+
+    boxContent = createGlobalElement("div");
+    boxContent.id = "fbBoxContent";
+    boxContentStyle = boxContent.style;
+    boxContentStyle.cssText = inspectContentStyle;
+    boxModel.appendChild(boxContent);
+
+    offlineFragment.appendChild(boxModel);
+};
+
+var destroyBoxModelInspector = function destroyBoxModelInspector()
+{
+    boxModel.parentNode.removeChild(boxModel);
+};
+
+// ************************************************************************************************
+// Section
+
+
+
+
+// ************************************************************************************************
+}});
+
+// Problems in IE
+// FIXED - eval return
+// FIXED - addEventListener problem in IE
+// FIXED doc.createRange?
+//
+// class reserved word
+// test all honza examples in IE6 and IE7
+
+
+/* See license.txt for terms of usage */
+
+( /** @scope s_domplate */ function() {
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+/** @class */
+FBL.DomplateTag = function DomplateTag(tagName)
+{
+    this.tagName = tagName;
+};
+
+/**
+ * @class
+ * @extends FBL.DomplateTag
+ */
+FBL.DomplateEmbed = function DomplateEmbed()
+{
+};
+
+/**
+ * @class
+ * @extends FBL.DomplateTag
+ */
+FBL.DomplateLoop = function DomplateLoop()
+{
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var DomplateTag = FBL.DomplateTag;
+var DomplateEmbed = FBL.DomplateEmbed;
+var DomplateLoop = FBL.DomplateLoop;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var womb = null;
+
+FBL.domplate = function()
+{
+    var lastSubject;
+    for (var i = 0; i < arguments.length; ++i)
+        lastSubject = lastSubject ? copyObject(lastSubject, arguments[i]) : arguments[i];
+
+    for (var name in lastSubject)
+    {
+        var val = lastSubject[name];
+        if (isTag(val))
+            val.tag.subject = lastSubject;
+    }
+
+    return lastSubject;
+};
+
+var domplate = FBL.domplate;
+
+FBL.domplate.context = function(context, fn)
+{
+    var lastContext = domplate.lastContext;
+    domplate.topContext = context;
+    fn.apply(context);
+    domplate.topContext = lastContext;
+};
+
+FBL.TAG = function()
+{
+    var embed = new DomplateEmbed();
+    return embed.merge(arguments);
+};
+
+FBL.FOR = function()
+{
+    var loop = new DomplateLoop();
+    return loop.merge(arguments);
+};
+
+FBL.DomplateTag.prototype =
+{
+    merge: function(args, oldTag)
+    {
+        if (oldTag)
+            this.tagName = oldTag.tagName;
+
+        this.context = oldTag ? oldTag.context : null;
+        this.subject = oldTag ? oldTag.subject : null;
+        this.attrs = oldTag ? copyObject(oldTag.attrs) : {};
+        this.classes = oldTag ? copyObject(oldTag.classes) : {};
+        this.props = oldTag ? copyObject(oldTag.props) : null;
+        this.listeners = oldTag ? copyArray(oldTag.listeners) : null;
+        this.children = oldTag ? copyArray(oldTag.children) : [];
+        this.vars = oldTag ? copyArray(oldTag.vars) : [];
+
+        var attrs = args.length ? args[0] : null;
+        var hasAttrs = typeof(attrs) == "object" && !isTag(attrs);
+
+        this.children = [];
+
+        if (domplate.topContext)
+            this.context = domplate.topContext;
+
+        if (args.length)
+            parseChildren(args, hasAttrs ? 1 : 0, this.vars, this.children);
+
+        if (hasAttrs)
+            this.parseAttrs(attrs);
+
+        return creator(this, DomplateTag);
+    },
+
+    parseAttrs: function(args)
+    {
+        for (var name in args)
+        {
+            var val = parseValue(args[name]);
+            readPartNames(val, this.vars);
+
+            if (name.indexOf("on") == 0)
+            {
+                var eventName = name.substr(2);
+                if (!this.listeners)
+                    this.listeners = [];
+                this.listeners.push(eventName, val);
+            }
+            else if (name.indexOf("_") == 0)
+            {
+                var propName = name.substr(1);
+                if (!this.props)
+                    this.props = {};
+                this.props[propName] = val;
+            }
+            else if (name.indexOf("$") == 0)
+            {
+                var className = name.substr(1);
+                if (!this.classes)
+                    this.classes = {};
+                this.classes[className] = val;
+            }
+            else
+            {
+                if (name == "class" && this.attrs.hasOwnProperty(name) )
+                    this.attrs[name] += " " + val;
+                else
+                    this.attrs[name] = val;
+            }
+        }
+    },
+
+    compile: function()
+    {
+        if (this.renderMarkup)
+            return;
+
+        this.compileMarkup();
+        this.compileDOM();
+
+        //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderMarkup: ", this.renderMarkup);
+        //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate renderDOM:", this.renderDOM);
+        //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate domArgs:", this.domArgs);
+    },
+
+    compileMarkup: function()
+    {
+        this.markupArgs = [];
+        var topBlock = [], topOuts = [], blocks = [], info = {args: this.markupArgs, argIndex: 0};
+
+        this.generateMarkup(topBlock, topOuts, blocks, info);
+        this.addCode(topBlock, topOuts, blocks);
+
+        var fnBlock = ['r=(function (__code__, __context__, __in__, __out__'];
+        for (var i = 0; i < info.argIndex; ++i)
+            fnBlock.push(', s', i);
+        fnBlock.push(') {');
+
+        if (this.subject)
+            fnBlock.push('with (this) {');
+        if (this.context)
+            fnBlock.push('with (__context__) {');
+        fnBlock.push('with (__in__) {');
+
+        fnBlock.push.apply(fnBlock, blocks);
+
+        if (this.subject)
+            fnBlock.push('}');
+        if (this.context)
+            fnBlock.push('}');
+
+        fnBlock.push('}})');
+
+        function __link__(tag, code, outputs, args)
+        {
+            if (!tag || !tag.tag)
+                return;
+
+            tag.tag.compile();
+
+            var tagOutputs = [];
+            var markupArgs = [code, tag.tag.context, args, tagOutputs];
+            markupArgs.push.apply(markupArgs, tag.tag.markupArgs);
+            tag.tag.renderMarkup.apply(tag.tag.subject, markupArgs);
+
+            outputs.push(tag);
+            outputs.push(tagOutputs);
+        }
+
+        function __escape__(value)
+        {
+            function replaceChars(ch)
+            {
+                switch (ch)
+                {
+                    case "<":
+                        return "&lt;";
+                    case ">":
+                        return "&gt;";
+                    case "&":
+                        return "&amp;";
+                    case "'":
+                        return "&#39;";
+                    case '"':
+                        return "&quot;";
+                }
+                return "?";
+            };
+            return String(value).replace(/[<>&"']/g, replaceChars);
+        }
+
+        function __loop__(iter, outputs, fn)
+        {
+            var iterOuts = [];
+            outputs.push(iterOuts);
+
+            if (iter instanceof Array)
+                iter = new ArrayIterator(iter);
+
+            try
+            {
+                while (1)
+                {
+                    var value = iter.next();
+                    var itemOuts = [0,0];
+                    iterOuts.push(itemOuts);
+                    fn.apply(this, [value, itemOuts]);
+                }
+            }
+            catch (exc)
+            {
+                if (exc != StopIteration)
+                    throw exc;
+            }
+        }
+
+        var js = fnBlock.join("");
+        var r = null;
+        eval(js);
+        this.renderMarkup = r;
+    },
+
+    getVarNames: function(args)
+    {
+        if (this.vars)
+            args.push.apply(args, this.vars);
+
+        for (var i = 0; i < this.children.length; ++i)
+        {
+            var child = this.children[i];
+            if (isTag(child))
+                child.tag.getVarNames(args);
+            else if (child instanceof Parts)
+            {
+                for (var i = 0; i < child.parts.length; ++i)
+                {
+                    if (child.parts[i] instanceof Variable)
+                    {
+                        var name = child.parts[i].name;
+                        var names = name.split(".");
+                        args.push(names[0]);
+                    }
+                }
+            }
+        }
+    },
+
+    generateMarkup: function(topBlock, topOuts, blocks, info)
+    {
+        topBlock.push(',"<', this.tagName, '"');
+
+        for (var name in this.attrs)
+        {
+            if (name != "class")
+            {
+                var val = this.attrs[name];
+                topBlock.push(', " ', name, '=\\""');
+                addParts(val, ',', topBlock, info, true);
+                topBlock.push(', "\\""');
+            }
+        }
+
+        if (this.listeners)
+        {
+            for (var i = 0; i < this.listeners.length; i += 2)
+                readPartNames(this.listeners[i+1], topOuts);
+        }
+
+        if (this.props)
+        {
+            for (var name in this.props)
+                readPartNames(this.props[name], topOuts);
+        }
+
+        if ( this.attrs.hasOwnProperty("class") || this.classes)
+        {
+            topBlock.push(', " class=\\""');
+            if (this.attrs.hasOwnProperty("class"))
+                addParts(this.attrs["class"], ',', topBlock, info, true);
+              topBlock.push(', " "');
+            for (var name in this.classes)
+            {
+                topBlock.push(', (');
+                addParts(this.classes[name], '', topBlock, info);
+                topBlock.push(' ? "', name, '" + " " : "")');
+            }
+            topBlock.push(', "\\""');
+        }
+        topBlock.push(',">"');
+
+        this.generateChildMarkup(topBlock, topOuts, blocks, info);
+        topBlock.push(',"</', this.tagName, '>"');
+    },
+
+    generateChildMarkup: function(topBlock, topOuts, blocks, info)
+    {
+        for (var i = 0; i < this.children.length; ++i)
+        {
+            var child = this.children[i];
+            if (isTag(child))
+                child.tag.generateMarkup(topBlock, topOuts, blocks, info);
+            else
+                addParts(child, ',', topBlock, info, true);
+        }
+    },
+
+    addCode: function(topBlock, topOuts, blocks)
+    {
+        if (topBlock.length)
+            blocks.push('__code__.push(""', topBlock.join(""), ');');
+        if (topOuts.length)
+            blocks.push('__out__.push(', topOuts.join(","), ');');
+        topBlock.splice(0, topBlock.length);
+        topOuts.splice(0, topOuts.length);
+    },
+
+    addLocals: function(blocks)
+    {
+        var varNames = [];
+        this.getVarNames(varNames);
+
+        var map = {};
+        for (var i = 0; i < varNames.length; ++i)
+        {
+            var name = varNames[i];
+            if ( map.hasOwnProperty(name) )
+                continue;
+
+            map[name] = 1;
+            var names = name.split(".");
+            blocks.push('var ', names[0] + ' = ' + '__in__.' + names[0] + ';');
+        }
+    },
+
+    compileDOM: function()
+    {
+        var path = [];
+        var blocks = [];
+        this.domArgs = [];
+        path.embedIndex = 0;
+        path.loopIndex = 0;
+        path.staticIndex = 0;
+        path.renderIndex = 0;
+        var nodeCount = this.generateDOM(path, blocks, this.domArgs);
+
+        var fnBlock = ['r=(function (root, context, o'];
+
+        for (var i = 0; i < path.staticIndex; ++i)
+            fnBlock.push(', ', 's'+i);
+
+        for (var i = 0; i < path.renderIndex; ++i)
+            fnBlock.push(', ', 'd'+i);
+
+        fnBlock.push(') {');
+        for (var i = 0; i < path.loopIndex; ++i)
+            fnBlock.push('var l', i, ' = 0;');
+        for (var i = 0; i < path.embedIndex; ++i)
+            fnBlock.push('var e', i, ' = 0;');
+
+        if (this.subject)
+            fnBlock.push('with (this) {');
+        if (this.context)
+            fnBlock.push('with (context) {');
+
+        fnBlock.push(blocks.join(""));
+
+        if (this.subject)
+            fnBlock.push('}');
+        if (this.context)
+            fnBlock.push('}');
+
+        fnBlock.push('return ', nodeCount, ';');
+        fnBlock.push('})');
+
+        function __bind__(object, fn)
+        {
+            return function(event) { return fn.apply(object, [event]); };
+        }
+
+        function __link__(node, tag, args)
+        {
+            if (!tag || !tag.tag)
+                return;
+
+            tag.tag.compile();
+
+            var domArgs = [node, tag.tag.context, 0];
+            domArgs.push.apply(domArgs, tag.tag.domArgs);
+            domArgs.push.apply(domArgs, args);
+            //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate__link__ domArgs:", domArgs);
+            return tag.tag.renderDOM.apply(tag.tag.subject, domArgs);
+        }
+
+        var self = this;
+        function __loop__(iter, fn)
+        {
+            var nodeCount = 0;
+            for (var i = 0; i < iter.length; ++i)
+            {
+                iter[i][0] = i;
+                iter[i][1] = nodeCount;
+                nodeCount += fn.apply(this, iter[i]);
+                //if (FBTrace.DBG_DOM) FBTrace.sysout("nodeCount", nodeCount);
+            }
+            return nodeCount;
+        }
+
+        function __path__(parent, offset)
+        {
+            //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate __path__ offset: "+ offset+"\n");
+            var root = parent;
+
+            for (var i = 2; i < arguments.length; ++i)
+            {
+                var index = arguments[i];
+                if (i == 3)
+                    index += offset;
+
+                if (index == -1)
+                    parent = parent.parentNode;
+                else
+                    parent = parent.childNodes[index];
+            }
+
+            //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate: "+arguments[2]+", root: "+ root+", parent: "+ parent+"\n");
+            return parent;
+        }
+
+        var js = fnBlock.join("");
+        //if (FBTrace.DBG_DOM) FBTrace.sysout(js.replace(/(\;|\{)/g, "$1\n"));
+        var r = null;
+        eval(js);
+        this.renderDOM = r;
+    },
+
+    generateDOM: function(path, blocks, args)
+    {
+        if (this.listeners || this.props)
+            this.generateNodePath(path, blocks);
+
+        if (this.listeners)
+        {
+            for (var i = 0; i < this.listeners.length; i += 2)
+            {
+                var val = this.listeners[i+1];
+                var arg = generateArg(val, path, args);
+                //blocks.push('node.addEventListener("', this.listeners[i], '", __bind__(this, ', arg, '), false);');
+                blocks.push('addEvent(node, "', this.listeners[i], '", __bind__(this, ', arg, '), false);');
+            }
+        }
+
+        if (this.props)
+        {
+            for (var name in this.props)
+            {
+                var val = this.props[name];
+                var arg = generateArg(val, path, args);
+                blocks.push('node.', name, ' = ', arg, ';');
+            }
+        }
+
+        this.generateChildDOM(path, blocks, args);
+        return 1;
+    },
+
+    generateNodePath: function(path, blocks)
+    {
+        blocks.push("var node = __path__(root, o");
+        for (var i = 0; i < path.length; ++i)
+            blocks.push(",", path[i]);
+        blocks.push(");");
+    },
+
+    generateChildDOM: function(path, blocks, args)
+    {
+        path.push(0);
+        for (var i = 0; i < this.children.length; ++i)
+        {
+            var child = this.children[i];
+            if (isTag(child))
+                path[path.length-1] += '+' + child.tag.generateDOM(path, blocks, args);
+            else
+                path[path.length-1] += '+1';
+        }
+        path.pop();
+    }
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+FBL.DomplateEmbed.prototype = copyObject(FBL.DomplateTag.prototype,
+/** @lends FBL.DomplateEmbed.prototype */
+{
+    merge: function(args, oldTag)
+    {
+        this.value = oldTag ? oldTag.value : parseValue(args[0]);
+        this.attrs = oldTag ? oldTag.attrs : {};
+        this.vars = oldTag ? copyArray(oldTag.vars) : [];
+
+        var attrs = args[1];
+        for (var name in attrs)
+        {
+            var val = parseValue(attrs[name]);
+            this.attrs[name] = val;
+            readPartNames(val, this.vars);
+        }
+
+        return creator(this, DomplateEmbed);
+    },
+
+    getVarNames: function(names)
+    {
+        if (this.value instanceof Parts)
+            names.push(this.value.parts[0].name);
+
+        if (this.vars)
+            names.push.apply(names, this.vars);
+    },
+
+    generateMarkup: function(topBlock, topOuts, blocks, info)
+    {
+        this.addCode(topBlock, topOuts, blocks);
+
+        blocks.push('__link__(');
+        addParts(this.value, '', blocks, info);
+        blocks.push(', __code__, __out__, {');
+
+        var lastName = null;
+        for (var name in this.attrs)
+        {
+            if (lastName)
+                blocks.push(',');
+            lastName = name;
+
+            var val = this.attrs[name];
+            blocks.push('"', name, '":');
+            addParts(val, '', blocks, info);
+        }
+
+        blocks.push('});');
+        //this.generateChildMarkup(topBlock, topOuts, blocks, info);
+    },
+
+    generateDOM: function(path, blocks, args)
+    {
+        var embedName = 'e'+path.embedIndex++;
+
+        this.generateNodePath(path, blocks);
+
+        var valueName = 'd' + path.renderIndex++;
+        var argsName = 'd' + path.renderIndex++;
+        blocks.push(embedName + ' = __link__(node, ', valueName, ', ', argsName, ');');
+
+        return embedName;
+    }
+});
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+FBL.DomplateLoop.prototype = copyObject(FBL.DomplateTag.prototype,
+/** @lends FBL.DomplateLoop.prototype */
+{
+    merge: function(args, oldTag)
+    {
+        this.varName = oldTag ? oldTag.varName : args[0];
+        this.iter = oldTag ? oldTag.iter : parseValue(args[1]);
+        this.vars = [];
+
+        this.children = oldTag ? copyArray(oldTag.children) : [];
+
+        var offset = Math.min(args.length, 2);
+        parseChildren(args, offset, this.vars, this.children);
+
+        return creator(this, DomplateLoop);
+    },
+
+    getVarNames: function(names)
+    {
+        if (this.iter instanceof Parts)
+            names.push(this.iter.parts[0].name);
+
+        DomplateTag.prototype.getVarNames.apply(this, [names]);
+    },
+
+    generateMarkup: function(topBlock, topOuts, blocks, info)
+    {
+        this.addCode(topBlock, topOuts, blocks);
+
+        var iterName;
+        if (this.iter instanceof Parts)
+        {
+            var part = this.iter.parts[0];
+            iterName = part.name;
+
+            if (part.format)
+            {
+                for (var i = 0; i < part.format.length; ++i)
+                    iterName = part.format[i] + "(" + iterName + ")";
+            }
+        }
+        else
+            iterName = this.iter;
+
+        blocks.push('__loop__.apply(this, [', iterName, ', __out__, function(', this.varName, ', __out__) {');
+        this.generateChildMarkup(topBlock, topOuts, blocks, info);
+        this.addCode(topBlock, topOuts, blocks);
+        blocks.push('}]);');
+    },
+
+    generateDOM: function(path, blocks, args)
+    {
+        var iterName = 'd'+path.renderIndex++;
+        var counterName = 'i'+path.loopIndex;
+        var loopName = 'l'+path.loopIndex++;
+
+        if (!path.length)
+            path.push(-1, 0);
+
+        var preIndex = path.renderIndex;
+        path.renderIndex = 0;
+
+        var nodeCount = 0;
+
+        var subBlocks = [];
+        var basePath = path[path.length-1];
+        for (var i = 0; i < this.children.length; ++i)
+        {
+            path[path.length-1] = basePath+'+'+loopName+'+'+nodeCount;
+
+            var child = this.children[i];
+            if (isTag(child))
+                nodeCount += '+' + child.tag.generateDOM(path, subBlocks, args);
+            else
+                nodeCount += '+1';
+        }
+
+        path[path.length-1] = basePath+'+'+loopName;
+
+        blocks.push(loopName,' = __loop__.apply(this, [', iterName, ', function(', counterName,',',loopName);
+        for (var i = 0; i < path.renderIndex; ++i)
+            blocks.push(',d'+i);
+        blocks.push(') {');
+        blocks.push(subBlocks.join(""));
+        blocks.push('return ', nodeCount, ';');
+        blocks.push('}]);');
+
+        path.renderIndex = preIndex;
+
+        return loopName;
+    }
+});
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+/** @class */
+function Variable(name, format)
+{
+    this.name = name;
+    this.format = format;
+}
+
+/** @class */
+function Parts(parts)
+{
+    this.parts = parts;
+}
+
+// ************************************************************************************************
+
+function parseParts(str)
+{
+    var re = /\$([_A-Za-z][_A-Za-z0-9.|]*)/g;
+    var index = 0;
+    var parts = [];
+
+    var m;
+    while (m = re.exec(str))
+    {
+        var pre = str.substr(index, (re.lastIndex-m[0].length)-index);
+        if (pre)
+            parts.push(pre);
+
+        var expr = m[1].split("|");
+        parts.push(new Variable(expr[0], expr.slice(1)));
+        index = re.lastIndex;
+    }
+
+    if (!index)
+        return str;
+
+    var post = str.substr(index);
+    if (post)
+        parts.push(post);
+
+    return new Parts(parts);
+}
+
+function parseValue(val)
+{
+    return typeof(val) == 'string' ? parseParts(val) : val;
+}
+
+function parseChildren(args, offset, vars, children)
+{
+    for (var i = offset; i < args.length; ++i)
+    {
+        var val = parseValue(args[i]);
+        children.push(val);
+        readPartNames(val, vars);
+    }
+}
+
+function readPartNames(val, vars)
+{
+    if (val instanceof Parts)
+    {
+        for (var i = 0; i < val.parts.length; ++i)
+        {
+            var part = val.parts[i];
+            if (part instanceof Variable)
+                vars.push(part.name);
+        }
+    }
+}
+
+function generateArg(val, path, args)
+{
+    if (val instanceof Parts)
+    {
+        var vals = [];
+        for (var i = 0; i < val.parts.length; ++i)
+        {
+            var part = val.parts[i];
+            if (part instanceof Variable)
+            {
+                var varName = 'd'+path.renderIndex++;
+                if (part.format)
+                {
+                    for (var j = 0; j < part.format.length; ++j)
+                        varName = part.format[j] + '(' + varName + ')';
+                }
+
+                vals.push(varName);
+            }
+            else
+                vals.push('"'+part.replace(/"/g, '\\"')+'"');
+        }
+
+        return vals.join('+');
+    }
+    else
+    {
+        args.push(val);
+        return 's' + path.staticIndex++;
+    }
+}
+
+function addParts(val, delim, block, info, escapeIt)
+{
+    var vals = [];
+    if (val instanceof Parts)
+    {
+        for (var i = 0; i < val.parts.length; ++i)
+        {
+            var part = val.parts[i];
+            if (part instanceof Variable)
+            {
+                var partName = part.name;
+                if (part.format)
+                {
+                    for (var j = 0; j < part.format.length; ++j)
+                        partName = part.format[j] + "(" + partName + ")";
+                }
+
+                if (escapeIt)
+                    vals.push("__escape__(" + partName + ")");
+                else
+                    vals.push(partName);
+            }
+            else
+                vals.push('"'+ part + '"');
+        }
+    }
+    else if (isTag(val))
+    {
+        info.args.push(val);
+        vals.push('s'+info.argIndex++);
+    }
+    else
+        vals.push('"'+ val + '"');
+
+    var parts = vals.join(delim);
+    if (parts)
+        block.push(delim, parts);
+}
+
+function isTag(obj)
+{
+    return (typeof(obj) == "function" || obj instanceof Function) && !!obj.tag;
+}
+
+function creator(tag, cons)
+{
+    var fn = new Function(
+        "var tag = arguments.callee.tag;" +
+        "var cons = arguments.callee.cons;" +
+        "var newTag = new cons();" +
+        "return newTag.merge(arguments, tag);");
+
+    fn.tag = tag;
+    fn.cons = cons;
+    extend(fn, Renderer);
+
+    return fn;
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+function copyArray(oldArray)
+{
+    var ary = [];
+    if (oldArray)
+        for (var i = 0; i < oldArray.length; ++i)
+            ary.push(oldArray[i]);
+   return ary;
+}
+
+function copyObject(l, r)
+{
+    var m = {};
+    extend(m, l);
+    extend(m, r);
+    return m;
+}
+
+function extend(l, r)
+{
+    for (var n in r)
+        l[n] = r[n];
+}
+
+function addEvent(object, name, handler)
+{
+    if (document.all)
+        object.attachEvent("on"+name, handler);
+    else
+        object.addEventListener(name, handler, false);
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+/** @class */
+function ArrayIterator(array)
+{
+    var index = -1;
+
+    this.next = function()
+    {
+        if (++index >= array.length)
+            throw StopIteration;
+
+        return array[index];
+    };
+}
+
+/** @class */
+function StopIteration() {}
+
+FBL.$break = function()
+{
+    throw StopIteration;
+};
+
+// ************************************************************************************************
+
+/** @namespace */
+var Renderer =
+{
+    renderHTML: function(args, outputs, self)
+    {
+        var code = [];
+        var markupArgs = [code, this.tag.context, args, outputs];
+        markupArgs.push.apply(markupArgs, this.tag.markupArgs);
+        this.tag.renderMarkup.apply(self ? self : this.tag.subject, markupArgs);
+        return code.join("");
+    },
+
+    insertRows: function(args, before, self)
+    {
+        this.tag.compile();
+
+        var outputs = [];
+        var html = this.renderHTML(args, outputs, self);
+
+        var doc = before.ownerDocument;
+        var div = doc.createElement("div");
+        div.innerHTML = "<table><tbody>"+html+"</tbody></table>";
+
+        var tbody = div.firstChild.firstChild;
+        var parent = before.tagName == "TR" ? before.parentNode : before;
+        var after = before.tagName == "TR" ? before.nextSibling : null;
+
+        var firstRow = tbody.firstChild, lastRow;
+        while (tbody.firstChild)
+        {
+            lastRow = tbody.firstChild;
+            if (after)
+                parent.insertBefore(lastRow, after);
+            else
+                parent.appendChild(lastRow);
+        }
+
+        var offset = 0;
+        if (before.tagName == "TR")
+        {
+            var node = firstRow.parentNode.firstChild;
+            for (; node && node != firstRow; node = node.nextSibling)
+                ++offset;
+        }
+
+        var domArgs = [firstRow, this.tag.context, offset];
+        domArgs.push.apply(domArgs, this.tag.domArgs);
+        domArgs.push.apply(domArgs, outputs);
+
+        this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
+        return [firstRow, lastRow];
+    },
+
+    insertBefore: function(args, before, self)
+    {
+        return this.insertNode(args, before.ownerDocument, before, false, self);
+    },
+
+    insertAfter: function(args, after, self)
+    {
+        return this.insertNode(args, after.ownerDocument, after, true, self);
+    },
+
+    insertNode: function(args, doc, element, isAfter, self)
+    {
+        if (!args)
+            args = {};
+
+        this.tag.compile();
+
+        var outputs = [];
+        var html = this.renderHTML(args, outputs, self);
+
+        //if (FBTrace.DBG_DOM)
+        //    FBTrace.sysout("domplate.insertNode html: "+html+"\n");
+
+        var doc = element.ownerDocument;
+        if (!womb || womb.ownerDocument != doc)
+            womb = doc.createElement("div");
+
+        womb.innerHTML = html;
+
+        var root = womb.firstChild;
+        if (isAfter)
+        {
+            while (womb.firstChild)
+                if (element.nextSibling)
+                    element.parentNode.insertBefore(womb.firstChild, element.nextSibling);
+                else
+                    element.parentNode.appendChild(womb.firstChild);
+        }
+        else
+        {
+            while (womb.lastChild)
+                element.parentNode.insertBefore(womb.lastChild, element);
+        }
+
+        var domArgs = [root, this.tag.context, 0];
+        domArgs.push.apply(domArgs, this.tag.domArgs);
+        domArgs.push.apply(domArgs, outputs);
+
+        //if (FBTrace.DBG_DOM)
+        //    FBTrace.sysout("domplate.insertNode domArgs:", domArgs);
+        this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
+
+        return root;
+    },
+    /**/
+
+    /*
+    insertAfter: function(args, before, self)
+    {
+        this.tag.compile();
+
+        var outputs = [];
+        var html = this.renderHTML(args, outputs, self);
+
+        var doc = before.ownerDocument;
+        if (!womb || womb.ownerDocument != doc)
+            womb = doc.createElement("div");
+
+        womb.innerHTML = html;
+
+        var root = womb.firstChild;
+        while (womb.firstChild)
+            if (before.nextSibling)
+                before.parentNode.insertBefore(womb.firstChild, before.nextSibling);
+            else
+                before.parentNode.appendChild(womb.firstChild);
+
+        var domArgs = [root, this.tag.context, 0];
+        domArgs.push.apply(domArgs, this.tag.domArgs);
+        domArgs.push.apply(domArgs, outputs);
+
+        this.tag.renderDOM.apply(self ? self : (this.tag.subject ? this.tag.subject : null),
+            domArgs);
+
+        return root;
+    },
+    /**/
+
+    replace: function(args, parent, self)
+    {
+        this.tag.compile();
+
+        var outputs = [];
+        var html = this.renderHTML(args, outputs, self);
+
+        var root;
+        if (parent.nodeType == 1)
+        {
+            parent.innerHTML = html;
+            root = parent.firstChild;
+        }
+        else
+        {
+            if (!parent || parent.nodeType != 9)
+                parent = document;
+
+            if (!womb || womb.ownerDocument != parent)
+                womb = parent.createElement("div");
+            womb.innerHTML = html;
+
+            root = womb.firstChild;
+            //womb.removeChild(root);
+        }
+
+        var domArgs = [root, this.tag.context, 0];
+        domArgs.push.apply(domArgs, this.tag.domArgs);
+        domArgs.push.apply(domArgs, outputs);
+        this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
+
+        return root;
+    },
+
+    append: function(args, parent, self)
+    {
+        this.tag.compile();
+
+        var outputs = [];
+        var html = this.renderHTML(args, outputs, self);
+        //if (FBTrace.DBG_DOM) FBTrace.sysout("domplate.append html: "+html+"\n");
+
+        if (!womb || womb.ownerDocument != parent.ownerDocument)
+            womb = parent.ownerDocument.createElement("div");
+        womb.innerHTML = html;
+
+        // TODO: xxxpedro domplate port to Firebug
+        var root = womb.firstChild;
+        while (womb.firstChild)
+            parent.appendChild(womb.firstChild);
+
+        // clearing element reference to avoid reference error in IE8 when switching contexts
+        womb = null;
+
+        var domArgs = [root, this.tag.context, 0];
+        domArgs.push.apply(domArgs, this.tag.domArgs);
+        domArgs.push.apply(domArgs, outputs);
+
+        //if (FBTrace.DBG_DOM) FBTrace.dumpProperties("domplate append domArgs:", domArgs);
+        this.tag.renderDOM.apply(self ? self : this.tag.subject, domArgs);
+
+        return root;
+    }
+};
+
+// ************************************************************************************************
+
+function defineTags()
+{
+    for (var i = 0; i < arguments.length; ++i)
+    {
+        var tagName = arguments[i];
+        var fn = new Function("var newTag = new arguments.callee.DomplateTag('"+tagName+"'); return newTag.merge(arguments);");
+        fn.DomplateTag = DomplateTag;
+
+        var fnName = tagName.toUpperCase();
+        FBL[fnName] = fn;
+    }
+}
+
+defineTags(
+    "a", "button", "br", "canvas", "code", "col", "colgroup", "div", "fieldset", "form", "h1", "h2", "h3", "hr",
+     "img", "input", "label", "legend", "li", "ol", "optgroup", "option", "p", "pre", "select",
+    "span", "strong", "table", "tbody", "td", "textarea", "tfoot", "th", "thead", "tr", "tt", "ul", "iframe"
+);
+
+})();
+
+
+/* See license.txt for terms of usage */
+
+var FirebugReps = FBL.ns(function() { with (FBL) {
+
+
+// ************************************************************************************************
+// Common Tags
+
+var OBJECTBOX = this.OBJECTBOX =
+    SPAN({"class": "objectBox objectBox-$className"});
+
+var OBJECTBLOCK = this.OBJECTBLOCK =
+    DIV({"class": "objectBox objectBox-$className"});
+
+var OBJECTLINK = this.OBJECTLINK = isIE6 ? // IE6 object link representation
+    A({
+        "class": "objectLink objectLink-$className a11yFocus",
+        href: "javascript:void(0)",
+        // workaround to show XPath (a better approach would use the tooltip on mouseover,
+        // so the XPath information would be calculated dynamically, but we need to create
+        // a tooltip class/wrapper around Menu or InfoTip)
+        title: "$object|FBL.getElementXPath",
+        _repObject: "$object"
+    })
+    : // Other browsers
+    A({
+        "class": "objectLink objectLink-$className a11yFocus",
+        // workaround to show XPath (a better approach would use the tooltip on mouseover,
+        // so the XPath information would be calculated dynamically, but we need to create
+        // a tooltip class/wrapper around Menu or InfoTip)
+        title: "$object|FBL.getElementXPath",
+        _repObject: "$object"
+    });
+
+
+// ************************************************************************************************
+
+this.Undefined = domplate(Firebug.Rep,
+{
+    tag: OBJECTBOX("undefined"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "undefined",
+
+    supportsObject: function(object, type)
+    {
+        return type == "undefined";
+    }
+});
+
+// ************************************************************************************************
+
+this.Null = domplate(Firebug.Rep,
+{
+    tag: OBJECTBOX("null"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "null",
+
+    supportsObject: function(object, type)
+    {
+        return object == null;
+    }
+});
+
+// ************************************************************************************************
+
+this.Nada = domplate(Firebug.Rep,
+{
+    tag: SPAN(""),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "nada"
+});
+
+// ************************************************************************************************
+
+this.Number = domplate(Firebug.Rep,
+{
+    tag: OBJECTBOX("$object"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "number",
+
+    supportsObject: function(object, type)
+    {
+        return type == "boolean" || type == "number";
+    }
+});
+
+// ************************************************************************************************
+
+this.String = domplate(Firebug.Rep,
+{
+    tag: OBJECTBOX("&quot;$object&quot;"),
+
+    shortTag: OBJECTBOX("&quot;$object|cropString&quot;"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "string",
+
+    supportsObject: function(object, type)
+    {
+        return type == "string";
+    }
+});
+
+// ************************************************************************************************
+
+this.Text = domplate(Firebug.Rep,
+{
+    tag: OBJECTBOX("$object"),
+
+    shortTag: OBJECTBOX("$object|cropString"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "text"
+});
+
+// ************************************************************************************************
+
+this.Caption = domplate(Firebug.Rep,
+{
+    tag: SPAN({"class": "caption"}, "$object")
+});
+
+// ************************************************************************************************
+
+this.Warning = domplate(Firebug.Rep,
+{
+    tag: DIV({"class": "warning focusRow", role : 'listitem'}, "$object|STR")
+});
+
+// ************************************************************************************************
+
+this.Func = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK("$object|summarizeFunction"),
+
+    summarizeFunction: function(fn)
+    {
+        var fnRegex = /function ([^(]+\([^)]*\)) \{/;
+        var fnText = safeToString(fn);
+
+        var m = fnRegex.exec(fnText);
+        return m ? m[1] : "function()";
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    copySource: function(fn)
+    {
+        copyToClipboard(safeToString(fn));
+    },
+
+    monitor: function(fn, script, monitored)
+    {
+        if (monitored)
+            Firebug.Debugger.unmonitorScript(fn, script, "monitor");
+        else
+            Firebug.Debugger.monitorScript(fn, script, "monitor");
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "function",
+
+    supportsObject: function(object, type)
+    {
+        return isFunction(object);
+    },
+
+    inspectObject: function(fn, context)
+    {
+        var sourceLink = findSourceForFunction(fn, context);
+        if (sourceLink)
+            Firebug.chrome.select(sourceLink);
+        if (FBTrace.DBG_FUNCTION_NAME)
+            FBTrace.sysout("reps.function.inspectObject selected sourceLink is ", sourceLink);
+    },
+
+    getTooltip: function(fn, context)
+    {
+        var script = findScriptForFunctionInContext(context, fn);
+        if (script)
+            return $STRF("Line", [normalizeURL(script.fileName), script.baseLineNumber]);
+        else
+            if (fn.toString)
+                return fn.toString();
+    },
+
+    getTitle: function(fn, context)
+    {
+        var name = fn.name ? fn.name : "function";
+        return name + "()";
+    },
+
+    getContextMenuItems: function(fn, target, context, script)
+    {
+        if (!script)
+            script = findScriptForFunctionInContext(context, fn);
+        if (!script)
+            return;
+
+        var scriptInfo = getSourceFileAndLineByScript(context, script);
+        var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false;
+
+        var name = script ? getFunctionName(script, context) : fn.name;
+        return [
+            {label: "CopySource", command: bindFixed(this.copySource, this, fn) },
+            "-",
+            {label: $STRF("ShowCallsInConsole", [name]), nol10n: true,
+             type: "checkbox", checked: monitored,
+             command: bindFixed(this.monitor, this, fn, script, monitored) }
+        ];
+    }
+});
+
+// ************************************************************************************************
+/*
+this.jsdScript = domplate(Firebug.Rep,
+{
+    copySource: function(script)
+    {
+        var fn = script.functionObject.getWrappedValue();
+        return FirebugReps.Func.copySource(fn);
+    },
+
+    monitor: function(fn, script, monitored)
+    {
+        fn = script.functionObject.getWrappedValue();
+        return FirebugReps.Func.monitor(fn, script, monitored);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "jsdScript",
+    inspectable: false,
+
+    supportsObject: function(object, type)
+    {
+        return object instanceof jsdIScript;
+    },
+
+    inspectObject: function(script, context)
+    {
+        var sourceLink = getSourceLinkForScript(script, context);
+        if (sourceLink)
+            Firebug.chrome.select(sourceLink);
+    },
+
+    getRealObject: function(script, context)
+    {
+        return script;
+    },
+
+    getTooltip: function(script)
+    {
+        return $STRF("jsdIScript", [script.tag]);
+    },
+
+    getTitle: function(script, context)
+    {
+        var fn = script.functionObject.getWrappedValue();
+        return FirebugReps.Func.getTitle(fn, context);
+    },
+
+    getContextMenuItems: function(script, target, context)
+    {
+        var fn = script.functionObject.getWrappedValue();
+
+        var scriptInfo = getSourceFileAndLineByScript(context, script);
+           var monitored = scriptInfo ? fbs.isMonitored(scriptInfo.sourceFile.href, scriptInfo.lineNo) : false;
+
+        var name = getFunctionName(script, context);
+
+        return [
+            {label: "CopySource", command: bindFixed(this.copySource, this, script) },
+            "-",
+            {label: $STRF("ShowCallsInConsole", [name]), nol10n: true,
+             type: "checkbox", checked: monitored,
+             command: bindFixed(this.monitor, this, fn, script, monitored) }
+        ];
+    }
+});
+/**/
+//************************************************************************************************
+
+this.Obj = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK(
+            SPAN({"class": "objectTitle"}, "$object|getTitle "),
+
+            SPAN({"class": "objectProps"},
+                SPAN({"class": "objectLeftBrace", role: "presentation"}, "{"),
+                FOR("prop", "$object|propIterator",
+                    SPAN({"class": "objectPropName", role: "presentation"}, "$prop.name"),
+                    SPAN({"class": "objectEqual", role: "presentation"}, "$prop.equal"),
+                    TAG("$prop.tag", {object: "$prop.object"}),
+                    SPAN({"class": "objectComma", role: "presentation"}, "$prop.delim")
+                ),
+                SPAN({"class": "objectRightBrace"}, "}")
+            )
+        ),
+
+    propNumberTag:
+        SPAN({"class": "objectProp-number"}, "$object"),
+
+    propStringTag:
+        SPAN({"class": "objectProp-string"}, "&quot;$object&quot;"),
+
+    propObjectTag:
+        SPAN({"class": "objectProp-object"}, "$object"),
+
+    propIterator: function (object)
+    {
+        ///Firebug.ObjectShortIteratorMax;
+        var maxLength = 55; // default max length for long representation
+
+        if (!object)
+            return [];
+
+        var props = [];
+        var length = 0;
+
+        var numProperties = 0;
+        var numPropertiesShown = 0;
+        var maxLengthReached = false;
+
+        var lib = this;
+
+        var propRepsMap =
+        {
+            "boolean": this.propNumberTag,
+            "number": this.propNumberTag,
+            "string": this.propStringTag,
+            "object": this.propObjectTag
+        };
+
+        try
+        {
+            var title = Firebug.Rep.getTitle(object);
+            length += title.length;
+
+            for (var name in object)
+            {
+                var value;
+                try
+                {
+                    value = object[name];
+                }
+                catch (exc)
+                {
+                    continue;
+                }
+
+                var type = typeof(value);
+                if (type == "boolean" ||
+                    type == "number" ||
+                    (type == "string" && value) ||
+                    (type == "object" && value && value.toString))
+                {
+                    var tag = propRepsMap[type];
+
+                    var value = (type == "object") ?
+                        Firebug.getRep(value).getTitle(value) :
+                        value + "";
+
+                    length += name.length + value.length + 4;
+
+                    if (length <= maxLength)
+                    {
+                        props.push({
+                            tag: tag,
+                            name: name,
+                            object: value,
+                            equal: "=",
+                            delim: ", "
+                        });
+
+                        numPropertiesShown++;
+                    }
+                    else
+                        maxLengthReached = true;
+
+                }
+
+                numProperties++;
+
+                if (maxLengthReached && numProperties > numPropertiesShown)
+                    break;
+            }
+
+            if (numProperties > numPropertiesShown)
+            {
+                props.push({
+                    object: "...", //xxxHonza localization
+                    tag: FirebugReps.Caption.tag,
+                    name: "",
+                    equal:"",
+                    delim:""
+                });
+            }
+            else if (props.length > 0)
+            {
+                props[props.length-1].delim = '';
+            }
+        }
+        catch (exc)
+        {
+            // Sometimes we get exceptions when trying to read from certain objects, like
+            // StorageList, but don't let that gum up the works
+            // XXXjjb also History.previous fails because object is a web-page object which does not have
+            // permission to read the history
+        }
+        return props;
+    },
+
+    fb_1_6_propIterator: function (object, max)
+    {
+        max = max || 3;
+        if (!object)
+            return [];
+
+        var props = [];
+        var len = 0, count = 0;
+
+        try
+        {
+            for (var name in object)
+            {
+                var value;
+                try
+                {
+                    value = object[name];
+                }
+                catch (exc)
+                {
+                    continue;
+                }
+
+                var t = typeof(value);
+                if (t == "boolean" || t == "number" || (t == "string" && value)
+                    || (t == "object" && value && value.toString))
+                {
+                    var rep = Firebug.getRep(value);
+                    var tag = rep.shortTag || rep.tag;
+                    if (t == "object")
+                    {
+                        value = rep.getTitle(value);
+                        tag = rep.titleTag;
+                    }
+                    count++;
+                    if (count <= max)
+                        props.push({tag: tag, name: name, object: value, equal: "=", delim: ", "});
+                    else
+                        break;
+                }
+            }
+            if (count > max)
+            {
+                props[Math.max(1,max-1)] = {
+                    object: "more...", //xxxHonza localization
+                    tag: FirebugReps.Caption.tag,
+                    name: "",
+                    equal:"",
+                    delim:""
+                };
+            }
+            else if (props.length > 0)
+            {
+                props[props.length-1].delim = '';
+            }
+        }
+        catch (exc)
+        {
+            // Sometimes we get exceptions when trying to read from certain objects, like
+            // StorageList, but don't let that gum up the works
+            // XXXjjb also History.previous fails because object is a web-page object which does not have
+            // permission to read the history
+        }
+        return props;
+    },
+
+    /*
+    propIterator: function (object)
+    {
+        if (!object)
+            return [];
+
+        var props = [];
+        var len = 0;
+
+        try
+        {
+            for (var name in object)
+            {
+                var val;
+                try
+                {
+                    val = object[name];
+                }
+                catch (exc)
+                {
+                    continue;
+                }
+
+                var t = typeof val;
+                if (t == "boolean" || t == "number" || (t == "string" && val)
+                    || (t == "object" && !isFunction(val) && val && val.toString))
+                {
+                    var title = (t == "object")
+                        ? Firebug.getRep(val).getTitle(val)
+                        : val+"";
+
+                    len += name.length + title.length + 1;
+                    if (len < 50)
+                        props.push({name: name, value: title});
+                    else
+                        break;
+                }
+            }
+        }
+        catch (exc)
+        {
+            // Sometimes we get exceptions when trying to read from certain objects, like
+            // StorageList, but don't let that gum up the works
+            // XXXjjb also History.previous fails because object is a web-page object which does not have
+            // permission to read the history
+        }
+
+        return props;
+    },
+    /**/
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "object",
+
+    supportsObject: function(object, type)
+    {
+        return true;
+    }
+});
+
+
+// ************************************************************************************************
+
+this.Arr = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTBOX({_repObject: "$object"},
+            SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["),
+            FOR("item", "$object|arrayIterator",
+                TAG("$item.tag", {object: "$item.object"}),
+                SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim")
+            ),
+            SPAN({"class": "arrayRightBracket", role : "presentation"}, "]")
+        ),
+
+    shortTag:
+        OBJECTBOX({_repObject: "$object"},
+            SPAN({"class": "arrayLeftBracket", role : "presentation"}, "["),
+            FOR("item", "$object|shortArrayIterator",
+                TAG("$item.tag", {object: "$item.object"}),
+                SPAN({"class": "arrayComma", role : "presentation"}, "$item.delim")
+            ),
+            // TODO: xxxpedro - confirm this on Firebug
+            //FOR("prop", "$object|shortPropIterator",
+            //        " $prop.name=",
+            //        SPAN({"class": "objectPropValue"}, "$prop.value|cropString")
+            //),
+            SPAN({"class": "arrayRightBracket"}, "]")
+        ),
+
+    arrayIterator: function(array)
+    {
+        var items = [];
+        for (var i = 0; i < array.length; ++i)
+        {
+            var value = array[i];
+            var rep = Firebug.getRep(value);
+            var tag = rep.shortTag ? rep.shortTag : rep.tag;
+            var delim = (i == array.length-1 ? "" : ", ");
+
+            items.push({object: value, tag: tag, delim: delim});
+        }
+
+        return items;
+    },
+
+    shortArrayIterator: function(array)
+    {
+        var items = [];
+        for (var i = 0; i < array.length && i < 3; ++i)
+        {
+            var value = array[i];
+            var rep = Firebug.getRep(value);
+            var tag = rep.shortTag ? rep.shortTag : rep.tag;
+            var delim = (i == array.length-1 ? "" : ", ");
+
+            items.push({object: value, tag: tag, delim: delim});
+        }
+
+        if (array.length > 3)
+            items.push({object: (array.length-3) + " more...", tag: FirebugReps.Caption.tag, delim: ""});
+
+        return items;
+    },
+
+    shortPropIterator:    this.Obj.propIterator,
+
+    getItemIndex: function(child)
+    {
+        var arrayIndex = 0;
+        for (child = child.previousSibling; child; child = child.previousSibling)
+        {
+            if (child.repObject)
+                ++arrayIndex;
+        }
+        return arrayIndex;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "array",
+
+    supportsObject: function(object)
+    {
+        return this.isArray(object);
+    },
+
+    // http://code.google.com/p/fbug/issues/detail?id=874
+    // BEGIN Yahoo BSD Source (modified here)  YAHOO.lang.isArray, YUI 2.2.2 June 2007
+    isArray: function(obj) {
+        try {
+            if (!obj)
+                return false;
+            else if (isIE && !isFunction(obj) && typeof obj == "object" && isFinite(obj.length) && obj.nodeType != 8)
+                return true;
+            else if (isFinite(obj.length) && isFunction(obj.splice))
+                return true;
+            else if (isFinite(obj.length) && isFunction(obj.callee)) // arguments
+                return true;
+            else if (instanceOf(obj, "HTMLCollection"))
+                return true;
+            else if (instanceOf(obj, "NodeList"))
+                return true;
+            else
+                return false;
+        }
+        catch(exc)
+        {
+            if (FBTrace.DBG_ERRORS)
+            {
+                FBTrace.sysout("isArray FAILS:", exc);  /* Something weird: without the try/catch, OOM, with no exception?? */
+                FBTrace.sysout("isArray Fails on obj", obj);
+            }
+        }
+
+        return false;
+    },
+    // END Yahoo BSD SOURCE See license below.
+
+    getTitle: function(object, context)
+    {
+        return "[" + object.length + "]";
+    }
+});
+
+// ************************************************************************************************
+
+this.Property = domplate(Firebug.Rep,
+{
+    supportsObject: function(object)
+    {
+        return object instanceof Property;
+    },
+
+    getRealObject: function(prop, context)
+    {
+        return prop.object[prop.name];
+    },
+
+    getTitle: function(prop, context)
+    {
+        return prop.name;
+    }
+});
+
+// ************************************************************************************************
+
+this.NetFile = domplate(this.Obj,
+{
+    supportsObject: function(object)
+    {
+        return object instanceof Firebug.NetFile;
+    },
+
+    browseObject: function(file, context)
+    {
+        openNewTab(file.href);
+        return true;
+    },
+
+    getRealObject: function(file, context)
+    {
+        return null;
+    }
+});
+
+// ************************************************************************************************
+
+this.Except = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTBOX({_repObject: "$object"}, "$object.message"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "exception",
+
+    supportsObject: function(object)
+    {
+        return object instanceof ErrorCopy;
+    }
+});
+
+
+// ************************************************************************************************
+
+this.Element = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK(
+            "&lt;",
+            SPAN({"class": "nodeTag"}, "$object.nodeName|toLowerCase"),
+            FOR("attr", "$object|attrIterator",
+                "&nbsp;$attr.nodeName=&quot;", SPAN({"class": "nodeValue"}, "$attr.nodeValue"), "&quot;"
+            ),
+            "&gt;"
+         ),
+
+    shortTag:
+        OBJECTLINK(
+            SPAN({"class": "$object|getVisible"},
+                SPAN({"class": "selectorTag"}, "$object|getSelectorTag"),
+                SPAN({"class": "selectorId"}, "$object|getSelectorId"),
+                SPAN({"class": "selectorClass"}, "$object|getSelectorClass"),
+                SPAN({"class": "selectorValue"}, "$object|getValue")
+            )
+         ),
+
+     getVisible: function(elt)
+     {
+         return isVisible(elt) ? "" : "selectorHidden";
+     },
+
+     getSelectorTag: function(elt)
+     {
+         return elt.nodeName.toLowerCase();
+     },
+
+     getSelectorId: function(elt)
+     {
+         return elt.id ? "#" + elt.id : "";
+     },
+
+     getSelectorClass: function(elt)
+     {
+         return elt.className ? "." + elt.className.split(" ")[0] : "";
+     },
+
+     getValue: function(elt)
+     {
+         // TODO: xxxpedro
+         return "";
+         var value;
+         if (elt instanceof HTMLImageElement)
+             value = getFileName(elt.src);
+         else if (elt instanceof HTMLAnchorElement)
+             value = getFileName(elt.href);
+         else if (elt instanceof HTMLInputElement)
+             value = elt.value;
+         else if (elt instanceof HTMLFormElement)
+             value = getFileName(elt.action);
+         else if (elt instanceof HTMLScriptElement)
+             value = getFileName(elt.src);
+
+         return value ? " " + cropString(value, 20) : "";
+     },
+
+     attrIterator: function(elt)
+     {
+         var attrs = [];
+         var idAttr, classAttr;
+         if (elt.attributes)
+         {
+             for (var i = 0; i < elt.attributes.length; ++i)
+             {
+                 var attr = elt.attributes[i];
+
+                 // we must check if the attribute is specified otherwise IE will show them
+                 if (!attr.specified || attr.nodeName && attr.nodeName.indexOf("firebug-") != -1)
+                    continue;
+                 else if (attr.nodeName == "id")
+                    idAttr = attr;
+                 else if (attr.nodeName == "class")
+                    classAttr = attr;
+                 else if (attr.nodeName == "style")
+                    attrs.push({
+                        nodeName: attr.nodeName,
+                        nodeValue: attr.nodeValue ||
+                        // IE won't recognize the attr.nodeValue of <style> nodes ...
+                        // and will return CSS property names in upper case, so we need to convert them
+                        elt.style.cssText.replace(/([^\s]+)\s*:/g,
+                                function(m,g){return g.toLowerCase()+":"})
+                    });
+                 else
+                    attrs.push(attr);
+             }
+         }
+         if (classAttr)
+            attrs.splice(0, 0, classAttr);
+         if (idAttr)
+            attrs.splice(0, 0, idAttr);
+
+         return attrs;
+     },
+
+     shortAttrIterator: function(elt)
+     {
+         var attrs = [];
+         if (elt.attributes)
+         {
+             for (var i = 0; i < elt.attributes.length; ++i)
+             {
+                 var attr = elt.attributes[i];
+                 if (attr.nodeName == "id" || attr.nodeName == "class")
+                     attrs.push(attr);
+             }
+         }
+
+         return attrs;
+     },
+
+     getHidden: function(elt)
+     {
+         return isVisible(elt) ? "" : "nodeHidden";
+     },
+
+     getXPath: function(elt)
+     {
+         return getElementTreeXPath(elt);
+     },
+
+     // TODO: xxxpedro remove this?
+     getNodeText: function(element)
+     {
+         var text = element.textContent;
+         if (Firebug.showFullTextNodes)
+            return text;
+        else
+            return cropString(text, 50);
+     },
+     /**/
+
+     getNodeTextGroups: function(element)
+     {
+         var text =  element.textContent;
+         if (!Firebug.showFullTextNodes)
+         {
+             text=cropString(text,50);
+         }
+
+         var escapeGroups=[];
+
+         if (Firebug.showTextNodesWithWhitespace)
+             escapeGroups.push({
+                'group': 'whitespace',
+                'class': 'nodeWhiteSpace',
+                'extra': {
+                    '\t': '_Tab',
+                    '\n': '_Para',
+                    ' ' : '_Space'
+                }
+             });
+         if (Firebug.showTextNodesWithEntities)
+             escapeGroups.push({
+                 'group':'text',
+                 'class':'nodeTextEntity',
+                 'extra':{}
+             });
+
+         if (escapeGroups.length)
+             return escapeGroupsForEntities(text, escapeGroups);
+         else
+             return [{str:text,'class':'',extra:''}];
+     },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    copyHTML: function(elt)
+    {
+        var html = getElementXML(elt);
+        copyToClipboard(html);
+    },
+
+    copyInnerHTML: function(elt)
+    {
+        copyToClipboard(elt.innerHTML);
+    },
+
+    copyXPath: function(elt)
+    {
+        var xpath = getElementXPath(elt);
+        copyToClipboard(xpath);
+    },
+
+    persistor: function(context, xpath)
+    {
+        var elts = xpath
+            ? getElementsByXPath(context.window.document, xpath)
+            : null;
+
+        return elts && elts.length ? elts[0] : null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "element",
+
+    supportsObject: function(object)
+    {
+        //return object instanceof Element || object.nodeType == 1 && typeof object.nodeName == "string";
+        return instanceOf(object, "Element");
+    },
+
+    browseObject: function(elt, context)
+    {
+        var tag = elt.nodeName.toLowerCase();
+        if (tag == "script")
+            openNewTab(elt.src);
+        else if (tag == "link")
+            openNewTab(elt.href);
+        else if (tag == "a")
+            openNewTab(elt.href);
+        else if (tag == "img")
+            openNewTab(elt.src);
+
+        return true;
+    },
+
+    persistObject: function(elt, context)
+    {
+        var xpath = getElementXPath(elt);
+
+        return bind(this.persistor, top, xpath);
+    },
+
+    getTitle: function(element, context)
+    {
+        return getElementCSSSelector(element);
+    },
+
+    getTooltip: function(elt)
+    {
+        return this.getXPath(elt);
+    },
+
+    getContextMenuItems: function(elt, target, context)
+    {
+        var monitored = areEventsMonitored(elt, null, context);
+
+        return [
+            {label: "CopyHTML", command: bindFixed(this.copyHTML, this, elt) },
+            {label: "CopyInnerHTML", command: bindFixed(this.copyInnerHTML, this, elt) },
+            {label: "CopyXPath", command: bindFixed(this.copyXPath, this, elt) },
+            "-",
+            {label: "ShowEventsInConsole", type: "checkbox", checked: monitored,
+             command: bindFixed(toggleMonitorEvents, FBL, elt, null, monitored, context) },
+            "-",
+            {label: "ScrollIntoView", command: bindFixed(elt.scrollIntoView, elt) }
+        ];
+    }
+});
+
+// ************************************************************************************************
+
+this.TextNode = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK(
+            "&lt;",
+            SPAN({"class": "nodeTag"}, "TextNode"),
+            "&nbsp;textContent=&quot;", SPAN({"class": "nodeValue"}, "$object.textContent|cropString"), "&quot;",
+            "&gt;"
+            ),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "textNode",
+
+    supportsObject: function(object)
+    {
+        return object instanceof Text;
+    }
+});
+
+// ************************************************************************************************
+
+this.Document = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK("Document ", SPAN({"class": "objectPropValue"}, "$object|getLocation")),
+
+    getLocation: function(doc)
+    {
+        return doc.location ? getFileName(doc.location.href) : "";
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "object",
+
+    supportsObject: function(object)
+    {
+        //return object instanceof Document || object instanceof XMLDocument;
+        return instanceOf(object, "Document");
+    },
+
+    browseObject: function(doc, context)
+    {
+        openNewTab(doc.location.href);
+        return true;
+    },
+
+    persistObject: function(doc, context)
+    {
+        return this.persistor;
+    },
+
+    persistor: function(context)
+    {
+        return context.window.document;
+    },
+
+    getTitle: function(win, context)
+    {
+        return "document";
+    },
+
+    getTooltip: function(doc)
+    {
+        return doc.location.href;
+    }
+});
+
+// ************************************************************************************************
+
+this.StyleSheet = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK("StyleSheet ", SPAN({"class": "objectPropValue"}, "$object|getLocation")),
+
+    getLocation: function(styleSheet)
+    {
+        return getFileName(styleSheet.href);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    copyURL: function(styleSheet)
+    {
+        copyToClipboard(styleSheet.href);
+    },
+
+    openInTab: function(styleSheet)
+    {
+        openNewTab(styleSheet.href);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "object",
+
+    supportsObject: function(object)
+    {
+        //return object instanceof CSSStyleSheet;
+        return instanceOf(object, "CSSStyleSheet");
+    },
+
+    browseObject: function(styleSheet, context)
+    {
+        openNewTab(styleSheet.href);
+        return true;
+    },
+
+    persistObject: function(styleSheet, context)
+    {
+        return bind(this.persistor, top, styleSheet.href);
+    },
+
+    getTooltip: function(styleSheet)
+    {
+        return styleSheet.href;
+    },
+
+    getContextMenuItems: function(styleSheet, target, context)
+    {
+        return [
+            {label: "CopyLocation", command: bindFixed(this.copyURL, this, styleSheet) },
+            "-",
+            {label: "OpenInTab", command: bindFixed(this.openInTab, this, styleSheet) }
+        ];
+    },
+
+    persistor: function(context, href)
+    {
+        return getStyleSheetByHref(href, context);
+    }
+});
+
+// ************************************************************************************************
+
+this.Window = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK("Window ", SPAN({"class": "objectPropValue"}, "$object|getLocation")),
+
+    getLocation: function(win)
+    {
+        try
+        {
+            return (win && win.location && !win.closed) ? getFileName(win.location.href) : "";
+        }
+        catch (exc)
+        {
+            if (FBTrace.DBG_ERRORS)
+                FBTrace.sysout("reps.Window window closed?");
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "object",
+
+    supportsObject: function(object)
+    {
+        return instanceOf(object, "Window");
+    },
+
+    browseObject: function(win, context)
+    {
+        openNewTab(win.location.href);
+        return true;
+    },
+
+    persistObject: function(win, context)
+    {
+        return this.persistor;
+    },
+
+    persistor: function(context)
+    {
+        return context.window;
+    },
+
+    getTitle: function(win, context)
+    {
+        return "window";
+    },
+
+    getTooltip: function(win)
+    {
+        if (win && !win.closed)
+            return win.location.href;
+    }
+});
+
+// ************************************************************************************************
+
+this.Event = domplate(Firebug.Rep,
+{
+    tag: TAG("$copyEventTag", {object: "$object|copyEvent"}),
+
+    copyEventTag:
+        OBJECTLINK("$object|summarizeEvent"),
+
+    summarizeEvent: function(event)
+    {
+        var info = [event.type, ' '];
+
+        var eventFamily = getEventFamily(event.type);
+        if (eventFamily == "mouse")
+            info.push("clientX=", event.clientX, ", clientY=", event.clientY);
+        else if (eventFamily == "key")
+            info.push("charCode=", event.charCode, ", keyCode=", event.keyCode);
+
+        return info.join("");
+    },
+
+    copyEvent: function(event)
+    {
+        return new EventCopy(event);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "object",
+
+    supportsObject: function(object)
+    {
+        //return object instanceof Event || object instanceof EventCopy;
+        return instanceOf(object, "Event") || instanceOf(object, "EventCopy");
+    },
+
+    getTitle: function(event, context)
+    {
+        return "Event " + event.type;
+    }
+});
+
+// ************************************************************************************************
+
+this.SourceLink = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTLINK({$collapsed: "$object|hideSourceLink"}, "$object|getSourceLinkTitle"),
+
+    hideSourceLink: function(sourceLink)
+    {
+        return sourceLink ? sourceLink.href.indexOf("XPCSafeJSObjectWrapper") != -1 : true;
+    },
+
+    getSourceLinkTitle: function(sourceLink)
+    {
+        if (!sourceLink)
+            return "";
+
+        try
+        {
+            var fileName = getFileName(sourceLink.href);
+            fileName = decodeURIComponent(fileName);
+            fileName = cropString(fileName, 17);
+        }
+        catch(exc)
+        {
+            if (FBTrace.DBG_ERRORS)
+                FBTrace.sysout("reps.getSourceLinkTitle decodeURIComponent fails for \'"+fileName+"\': "+exc, exc);
+        }
+
+        return typeof sourceLink.line == "number" ?
+                fileName + " (line " + sourceLink.line + ")" :
+                fileName;
+
+        // TODO: xxxpedro
+        //return $STRF("Line", [fileName, sourceLink.line]);
+    },
+
+    copyLink: function(sourceLink)
+    {
+        copyToClipboard(sourceLink.href);
+    },
+
+    openInTab: function(sourceLink)
+    {
+        openNewTab(sourceLink.href);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "sourceLink",
+
+    supportsObject: function(object)
+    {
+        return object instanceof SourceLink;
+    },
+
+    getTooltip: function(sourceLink)
+    {
+        return decodeURI(sourceLink.href);
+    },
+
+    inspectObject: function(sourceLink, context)
+    {
+        if (sourceLink.type == "js")
+        {
+            var scriptFile = getSourceFileByHref(sourceLink.href, context);
+            if (scriptFile)
+                return Firebug.chrome.select(sourceLink);
+        }
+        else if (sourceLink.type == "css")
+        {
+            // If an object is defined, treat it as the highest priority for
+            // inspect actions
+            if (sourceLink.object) {
+                Firebug.chrome.select(sourceLink.object);
+                return;
+            }
+
+            var stylesheet = getStyleSheetByHref(sourceLink.href, context);
+            if (stylesheet)
+            {
+                var ownerNode = stylesheet.ownerNode;
+                if (ownerNode)
+                {
+                    Firebug.chrome.select(sourceLink, "html");
+                    return;
+                }
+
+                var panel = context.getPanel("stylesheet");
+                if (panel && panel.getRuleByLine(stylesheet, sourceLink.line))
+                    return Firebug.chrome.select(sourceLink);
+            }
+        }
+
+        // Fallback is to just open the view-source window on the file
+        viewSource(sourceLink.href, sourceLink.line);
+    },
+
+    browseObject: function(sourceLink, context)
+    {
+        openNewTab(sourceLink.href);
+        return true;
+    },
+
+    getContextMenuItems: function(sourceLink, target, context)
+    {
+        return [
+            {label: "CopyLocation", command: bindFixed(this.copyLink, this, sourceLink) },
+            "-",
+            {label: "OpenInTab", command: bindFixed(this.openInTab, this, sourceLink) }
+        ];
+    }
+});
+
+// ************************************************************************************************
+
+this.SourceFile = domplate(this.SourceLink,
+{
+    tag:
+        OBJECTLINK({$collapsed: "$object|hideSourceLink"}, "$object|getSourceLinkTitle"),
+
+    persistor: function(context, href)
+    {
+        return getSourceFileByHref(href, context);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "sourceFile",
+
+    supportsObject: function(object)
+    {
+        return object instanceof SourceFile;
+    },
+
+    persistObject: function(sourceFile)
+    {
+        return bind(this.persistor, top, sourceFile.href);
+    },
+
+    browseObject: function(sourceLink, context)
+    {
+    },
+
+    getTooltip: function(sourceFile)
+    {
+        return sourceFile.href;
+    }
+});
+
+// ************************************************************************************************
+
+this.StackFrame = domplate(Firebug.Rep,  // XXXjjb Since the repObject is fn the stack does not have correct line numbers
+{
+    tag:
+        OBJECTBLOCK(
+            A({"class": "objectLink objectLink-function focusRow a11yFocus", _repObject: "$object.fn"}, "$object|getCallName"),
+            " ( ",
+            FOR("arg", "$object|argIterator",
+                TAG("$arg.tag", {object: "$arg.value"}),
+                SPAN({"class": "arrayComma"}, "$arg.delim")
+            ),
+            " )",
+            SPAN({"class": "objectLink-sourceLink objectLink"}, "$object|getSourceLinkTitle")
+        ),
+
+    getCallName: function(frame)
+    {
+        //TODO: xxxpedro reps StackFrame
+        return frame.name || "anonymous";
+
+        //return getFunctionName(frame.script, frame.context);
+    },
+
+    getSourceLinkTitle: function(frame)
+    {
+        //TODO: xxxpedro reps StackFrame
+        var fileName = cropString(getFileName(frame.href), 20);
+        return fileName + (frame.lineNo ? " (line " + frame.lineNo + ")" : "");
+
+        var fileName = cropString(getFileName(frame.href), 17);
+        return $STRF("Line", [fileName, frame.lineNo]);
+    },
+
+    argIterator: function(frame)
+    {
+        if (!frame.args)
+            return [];
+
+        var items = [];
+
+        for (var i = 0; i < frame.args.length; ++i)
+        {
+            var arg = frame.args[i];
+
+            if (!arg)
+                break;
+
+            var rep = Firebug.getRep(arg.value);
+            var tag = rep.shortTag ? rep.shortTag : rep.tag;
+
+            var delim = (i == frame.args.length-1 ? "" : ", ");
+
+            items.push({name: arg.name, value: arg.value, tag: tag, delim: delim});
+        }
+
+        return items;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "stackFrame",
+
+    supportsObject: function(object)
+    {
+        return object instanceof StackFrame;
+    },
+
+    inspectObject: function(stackFrame, context)
+    {
+        var sourceLink = new SourceLink(stackFrame.href, stackFrame.lineNo, "js");
+        Firebug.chrome.select(sourceLink);
+    },
+
+    getTooltip: function(stackFrame, context)
+    {
+        return $STRF("Line", [stackFrame.href, stackFrame.lineNo]);
+    }
+
+});
+
+// ************************************************************************************************
+
+this.StackTrace = domplate(Firebug.Rep,
+{
+    tag:
+        FOR("frame", "$object.frames focusRow",
+            TAG(this.StackFrame.tag, {object: "$frame"})
+        ),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "stackTrace",
+
+    supportsObject: function(object)
+    {
+        return object instanceof StackTrace;
+    }
+});
+
+// ************************************************************************************************
+
+this.jsdStackFrame = domplate(Firebug.Rep,
+{
+    inspectable: false,
+
+    supportsObject: function(object)
+    {
+        return (object instanceof jsdIStackFrame) && (object.isValid);
+    },
+
+    getTitle: function(frame, context)
+    {
+        if (!frame.isValid) return "(invalid frame)"; // XXXjjb avoid frame.script == null
+        return getFunctionName(frame.script, context);
+    },
+
+    getTooltip: function(frame, context)
+    {
+        if (!frame.isValid) return "(invalid frame)";  // XXXjjb avoid frame.script == null
+        var sourceInfo = FBL.getSourceFileAndLineByScript(context, frame.script, frame);
+        if (sourceInfo)
+            return $STRF("Line", [sourceInfo.sourceFile.href, sourceInfo.lineNo]);
+        else
+            return $STRF("Line", [frame.script.fileName, frame.line]);
+    },
+
+    getContextMenuItems: function(frame, target, context)
+    {
+        var fn = frame.script.functionObject.getWrappedValue();
+        return FirebugReps.Func.getContextMenuItems(fn, target, context, frame.script);
+    }
+});
+
+// ************************************************************************************************
+
+this.ErrorMessage = domplate(Firebug.Rep,
+{
+    tag:
+        OBJECTBOX({
+                $hasTwisty: "$object|hasStackTrace",
+                $hasBreakSwitch: "$object|hasBreakSwitch",
+                $breakForError: "$object|hasErrorBreak",
+                _repObject: "$object",
+                _stackTrace: "$object|getLastErrorStackTrace",
+                onclick: "$onToggleError"},
+
+            DIV({"class": "errorTitle a11yFocus", role : 'checkbox', 'aria-checked' : 'false'},
+                "$object.message|getMessage"
+            ),
+            DIV({"class": "errorTrace"}),
+            DIV({"class": "errorSourceBox errorSource-$object|getSourceType"},
+                IMG({"class": "errorBreak a11yFocus", src:"blank.gif", role : 'checkbox', 'aria-checked':'false', title: "Break on this error"}),
+                A({"class": "errorSource a11yFocus"}, "$object|getLine")
+            ),
+            TAG(this.SourceLink.tag, {object: "$object|getSourceLink"})
+        ),
+
+    getLastErrorStackTrace: function(error)
+    {
+        return error.trace;
+    },
+
+    hasStackTrace: function(error)
+    {
+        var url = error.href.toString();
+        var fromCommandLine = (url.indexOf("XPCSafeJSObjectWrapper") != -1);
+        return !fromCommandLine && error.trace;
+    },
+
+    hasBreakSwitch: function(error)
+    {
+        return error.href && error.lineNo > 0;
+    },
+
+    hasErrorBreak: function(error)
+    {
+        return fbs.hasErrorBreakpoint(error.href, error.lineNo);
+    },
+
+    getMessage: function(message)
+    {
+        var re = /\[Exception... "(.*?)" nsresult:/;
+        var m = re.exec(message);
+        return m ? m[1] : message;
+    },
+
+    getLine: function(error)
+    {
+        if (error.category == "js")
+        {
+            if (error.source)
+                return cropString(error.source, 80);
+            else if (error.href && error.href.indexOf("XPCSafeJSObjectWrapper") == -1)
+                return cropString(error.getSourceLine(), 80);
+        }
+    },
+
+    getSourceLink: function(error)
+    {
+        var ext = error.category == "css" ? "css" : "js";
+        return error.lineNo ? new SourceLink(error.href, error.lineNo, ext) : null;
+    },
+
+    getSourceType: function(error)
+    {
+        // Errors occurring inside of HTML event handlers look like "foo.html (line 1)"
+        // so let's try to skip those
+        if (error.source)
+            return "syntax";
+        else if (error.lineNo == 1 && getFileExtension(error.href) != "js")
+            return "none";
+        else if (error.category == "css")
+            return "none";
+        else if (!error.href || !error.lineNo)
+            return "none";
+        else
+            return "exec";
+    },
+
+    onToggleError: function(event)
+    {
+        var target = event.currentTarget;
+        if (hasClass(event.target, "errorBreak"))
+        {
+            this.breakOnThisError(target.repObject);
+        }
+        else if (hasClass(event.target, "errorSource"))
+        {
+            var panel = Firebug.getElementPanel(event.target);
+            this.inspectObject(target.repObject, panel.context);
+        }
+        else if (hasClass(event.target, "errorTitle"))
+        {
+            var traceBox = target.childNodes[1];
+            toggleClass(target, "opened");
+            event.target.setAttribute('aria-checked', hasClass(target, "opened"));
+            if (hasClass(target, "opened"))
+            {
+                if (target.stackTrace)
+                    var node = FirebugReps.StackTrace.tag.append({object: target.stackTrace}, traceBox);
+                if (Firebug.A11yModel.enabled)
+                {
+                    var panel = Firebug.getElementPanel(event.target);
+                    dispatch([Firebug.A11yModel], "onLogRowContentCreated", [panel , traceBox]);
+                }
+            }
+            else
+                clearNode(traceBox);
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    copyError: function(error)
+    {
+        var message = [
+            this.getMessage(error.message),
+            error.href,
+            "Line " +  error.lineNo
+        ];
+        copyToClipboard(message.join("\n"));
+    },
+
+    breakOnThisError: function(error)
+    {
+        if (this.hasErrorBreak(error))
+            Firebug.Debugger.clearErrorBreakpoint(error.href, error.lineNo);
+        else
+            Firebug.Debugger.setErrorBreakpoint(error.href, error.lineNo);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "errorMessage",
+    inspectable: false,
+
+    supportsObject: function(object)
+    {
+        return object instanceof ErrorMessage;
+    },
+
+    inspectObject: function(error, context)
+    {
+        var sourceLink = this.getSourceLink(error);
+        FirebugReps.SourceLink.inspectObject(sourceLink, context);
+    },
+
+    getContextMenuItems: function(error, target, context)
+    {
+        var breakOnThisError = this.hasErrorBreak(error);
+
+        var items = [
+            {label: "CopyError", command: bindFixed(this.copyError, this, error) }
+        ];
+
+        if (error.category == "css")
+        {
+            items.push(
+                "-",
+                {label: "BreakOnThisError", type: "checkbox", checked: breakOnThisError,
+                 command: bindFixed(this.breakOnThisError, this, error) },
+
+                optionMenu("BreakOnAllErrors", "breakOnErrors")
+            );
+        }
+
+        return items;
+    }
+});
+
+// ************************************************************************************************
+
+this.Assert = domplate(Firebug.Rep,
+{
+    tag:
+        DIV(
+            DIV({"class": "errorTitle"}),
+            DIV({"class": "assertDescription"})
+        ),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "assert",
+
+    inspectObject: function(error, context)
+    {
+        var sourceLink = this.getSourceLink(error);
+        Firebug.chrome.select(sourceLink);
+    },
+
+    getContextMenuItems: function(error, target, context)
+    {
+        var breakOnThisError = this.hasErrorBreak(error);
+
+        return [
+            {label: "CopyError", command: bindFixed(this.copyError, this, error) },
+            "-",
+            {label: "BreakOnThisError", type: "checkbox", checked: breakOnThisError,
+             command: bindFixed(this.breakOnThisError, this, error) },
+            {label: "BreakOnAllErrors", type: "checkbox", checked: Firebug.breakOnErrors,
+             command: bindFixed(this.breakOnAllErrors, this, error) }
+        ];
+    }
+});
+
+// ************************************************************************************************
+
+this.SourceText = domplate(Firebug.Rep,
+{
+    tag:
+        DIV(
+            FOR("line", "$object|lineIterator",
+                DIV({"class": "sourceRow", role : "presentation"},
+                    SPAN({"class": "sourceLine", role : "presentation"}, "$line.lineNo"),
+                    SPAN({"class": "sourceRowText", role : "presentation"}, "$line.text")
+                )
+            )
+        ),
+
+    lineIterator: function(sourceText)
+    {
+        var maxLineNoChars = (sourceText.lines.length + "").length;
+        var list = [];
+
+        for (var i = 0; i < sourceText.lines.length; ++i)
+        {
+            // Make sure all line numbers are the same width (with a fixed-width font)
+            var lineNo = (i+1) + "";
+            while (lineNo.length < maxLineNoChars)
+                lineNo = " " + lineNo;
+
+            list.push({lineNo: lineNo, text: sourceText.lines[i]});
+        }
+
+        return list;
+    },
+
+    getHTML: function(sourceText)
+    {
+        return getSourceLineRange(sourceText, 1, sourceText.lines.length);
+    }
+});
+
+//************************************************************************************************
+this.nsIDOMHistory = domplate(Firebug.Rep,
+{
+    tag:OBJECTBOX({onclick: "$showHistory"},
+            OBJECTLINK("$object|summarizeHistory")
+        ),
+
+    className: "nsIDOMHistory",
+
+    summarizeHistory: function(history)
+    {
+        try
+        {
+            var items = history.length;
+            return items + " history entries";
+        }
+        catch(exc)
+        {
+            return "object does not support history (nsIDOMHistory)";
+        }
+    },
+
+    showHistory: function(history)
+    {
+        try
+        {
+            var items = history.length;  // if this throws, then unsupported
+            Firebug.chrome.select(history);
+        }
+        catch (exc)
+        {
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    supportsObject: function(object, type)
+    {
+        return (object instanceof Ci.nsIDOMHistory);
+    }
+});
+
+// ************************************************************************************************
+this.ApplicationCache = domplate(Firebug.Rep,
+{
+    tag:OBJECTBOX({onclick: "$showApplicationCache"},
+            OBJECTLINK("$object|summarizeCache")
+        ),
+
+    summarizeCache: function(applicationCache)
+    {
+        try
+        {
+            return applicationCache.length + " items in offline cache";
+        }
+        catch(exc)
+        {
+            return "https://bugzilla.mozilla.org/show_bug.cgi?id=422264";
+        }
+    },
+
+    showApplicationCache: function(event)
+    {
+        openNewTab("https://bugzilla.mozilla.org/show_bug.cgi?id=422264");
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "applicationCache",
+
+    supportsObject: function(object, type)
+    {
+        if (Ci.nsIDOMOfflineResourceList)
+            return (object instanceof Ci.nsIDOMOfflineResourceList);
+    }
+
+});
+
+this.Storage = domplate(Firebug.Rep,
+{
+    tag: OBJECTBOX({onclick: "$show"}, OBJECTLINK("$object|summarize")),
+
+    summarize: function(storage)
+    {
+        return storage.length +" items in Storage";
+    },
+    show: function(storage)
+    {
+        openNewTab("http://dev.w3.org/html5/webstorage/#storage-0");
+    },
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    className: "Storage",
+
+    supportsObject: function(object, type)
+    {
+        return (object instanceof Storage);
+    }
+
+});
+
+// ************************************************************************************************
+Firebug.registerRep(
+    //this.nsIDOMHistory, // make this early to avoid exceptions
+    this.Undefined,
+    this.Null,
+    this.Number,
+    this.String,
+    this.Window,
+    //this.ApplicationCache, // must come before Arr (array) else exceptions.
+    //this.ErrorMessage,
+    this.Element,
+    //this.TextNode,
+    this.Document,
+    this.StyleSheet,
+    this.Event,
+    //this.SourceLink,
+    //this.SourceFile,
+    //this.StackTrace,
+    //this.StackFrame,
+    //this.jsdStackFrame,
+    //this.jsdScript,
+    //this.NetFile,
+    this.Property,
+    this.Except,
+    this.Arr
+);
+
+Firebug.setDefaultReps(this.Func, this.Obj);
+
+}});
+
+// ************************************************************************************************
+/*
+ * The following is http://developer.yahoo.com/yui/license.txt and applies to only code labeled "Yahoo BSD Source"
+ * in only this file reps.js.  John J. Barton June 2007.
+ *
+Software License Agreement (BSD License)
+
+Copyright (c) 2006, Yahoo! Inc.
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms, with or without modification, are
+permitted provided that the following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of Yahoo! Inc. nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of Yahoo! Inc.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
+PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ * /
+ */
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// Constants
+
+var saveTimeout = 400;
+var pageAmount = 10;
+
+// ************************************************************************************************
+// Globals
+
+var currentTarget = null;
+var currentGroup = null;
+var currentPanel = null;
+var currentEditor = null;
+
+var defaultEditor = null;
+
+var originalClassName = null;
+
+var originalValue = null;
+var defaultValue = null;
+var previousValue = null;
+
+var invalidEditor = false;
+var ignoreNextInput = false;
+
+// ************************************************************************************************
+
+Firebug.Editor = extend(Firebug.Module,
+{
+    supportsStopEvent: true,
+
+    dispatchName: "editor",
+    tabCharacter: "    ",
+
+    startEditing: function(target, value, editor)
+    {
+        this.stopEditing();
+
+        if (hasClass(target, "insertBefore") || hasClass(target, "insertAfter"))
+            return;
+
+        var panel = Firebug.getElementPanel(target);
+        if (!panel.editable)
+            return;
+
+        if (FBTrace.DBG_EDITOR)
+            FBTrace.sysout("editor.startEditing " + value, target);
+
+        defaultValue = target.getAttribute("defaultValue");
+        if (value == undefined)
+        {
+            var textContent = isIE ? "innerText" : "textContent";
+            value = target[textContent];
+            if (value == defaultValue)
+                value = "";
+        }
+
+        originalValue = previousValue = value;
+
+        invalidEditor = false;
+        currentTarget = target;
+        currentPanel = panel;
+        currentGroup = getAncestorByClass(target, "editGroup");
+
+        currentPanel.editing = true;
+
+        var panelEditor = currentPanel.getEditor(target, value);
+        currentEditor = editor ? editor : panelEditor;
+        if (!currentEditor)
+            currentEditor = getDefaultEditor(currentPanel);
+
+        var inlineParent = getInlineParent(target);
+        var targetSize = getOffsetSize(inlineParent);
+
+        setClass(panel.panelNode, "editing");
+        setClass(target, "editing");
+        if (currentGroup)
+            setClass(currentGroup, "editing");
+
+        currentEditor.show(target, currentPanel, value, targetSize);
+        //dispatch(this.fbListeners, "onBeginEditing", [currentPanel, currentEditor, target, value]);
+        currentEditor.beginEditing(target, value);
+        if (FBTrace.DBG_EDITOR)
+            FBTrace.sysout("Editor start panel "+currentPanel.name);
+        this.attachListeners(currentEditor, panel.context);
+    },
+
+    stopEditing: function(cancel)
+    {
+        if (!currentTarget)
+            return;
+
+        if (FBTrace.DBG_EDITOR)
+            FBTrace.sysout("editor.stopEditing cancel:" + cancel+" saveTimeout: "+this.saveTimeout);
+
+        clearTimeout(this.saveTimeout);
+        delete this.saveTimeout;
+
+        this.detachListeners(currentEditor, currentPanel.context);
+
+        removeClass(currentPanel.panelNode, "editing");
+        removeClass(currentTarget, "editing");
+        if (currentGroup)
+            removeClass(currentGroup, "editing");
+
+        var value = currentEditor.getValue();
+        if (value == defaultValue)
+            value = "";
+
+        var removeGroup = currentEditor.endEditing(currentTarget, value, cancel);
+
+        try
+        {
+            if (cancel)
+            {
+                //dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, removeGroup && !originalValue]);
+                if (value != originalValue)
+                    this.saveEditAndNotifyListeners(currentTarget, originalValue, previousValue);
+
+                if (removeGroup && !originalValue && currentGroup)
+                    currentGroup.parentNode.removeChild(currentGroup);
+            }
+            else if (!value)
+            {
+                this.saveEditAndNotifyListeners(currentTarget, null, previousValue);
+
+                if (removeGroup && currentGroup)
+                    currentGroup.parentNode.removeChild(currentGroup);
+            }
+            else
+                this.save(value);
+        }
+        catch (exc)
+        {
+            //throw exc.message;
+            //ERROR(exc);
+        }
+
+        currentEditor.hide();
+        currentPanel.editing = false;
+
+        //dispatch(this.fbListeners, "onStopEdit", [currentPanel, currentEditor, currentTarget]);
+        //if (FBTrace.DBG_EDITOR)
+        //    FBTrace.sysout("Editor stop panel "+currentPanel.name);
+
+        currentTarget = null;
+        currentGroup = null;
+        currentPanel = null;
+        currentEditor = null;
+        originalValue = null;
+        invalidEditor = false;
+
+        return value;
+    },
+
+    cancelEditing: function()
+    {
+        return this.stopEditing(true);
+    },
+
+    update: function(saveNow)
+    {
+        if (this.saveTimeout)
+            clearTimeout(this.saveTimeout);
+
+        invalidEditor = true;
+
+        currentEditor.layout();
+
+        if (saveNow)
+            this.save();
+        else
+        {
+            var context = currentPanel.context;
+            this.saveTimeout = context.setTimeout(bindFixed(this.save, this), saveTimeout);
+            if (FBTrace.DBG_EDITOR)
+                FBTrace.sysout("editor.update saveTimeout: "+this.saveTimeout);
+        }
+    },
+
+    save: function(value)
+    {
+        if (!invalidEditor)
+            return;
+
+        if (value == undefined)
+            value = currentEditor.getValue();
+        if (FBTrace.DBG_EDITOR)
+            FBTrace.sysout("editor.save saveTimeout: "+this.saveTimeout+" currentPanel: "+(currentPanel?currentPanel.name:"null"));
+        try
+        {
+            this.saveEditAndNotifyListeners(currentTarget, value, previousValue);
+
+            previousValue = value;
+            invalidEditor = false;
+        }
+        catch (exc)
+        {
+            if (FBTrace.DBG_ERRORS)
+                FBTrace.sysout("editor.save FAILS "+exc, exc);
+        }
+    },
+
+    saveEditAndNotifyListeners: function(currentTarget, value, previousValue)
+    {
+        currentEditor.saveEdit(currentTarget, value, previousValue);
+        //dispatch(this.fbListeners, "onSaveEdit", [currentPanel, currentEditor, currentTarget, value, previousValue]);
+    },
+
+    setEditTarget: function(element)
+    {
+        if (!element)
+        {
+            dispatch([Firebug.A11yModel], 'onInlineEditorClose', [currentPanel, currentTarget, true]);
+            this.stopEditing();
+        }
+        else if (hasClass(element, "insertBefore"))
+            this.insertRow(element, "before");
+        else if (hasClass(element, "insertAfter"))
+            this.insertRow(element, "after");
+        else
+            this.startEditing(element);
+    },
+
+    tabNextEditor: function()
+    {
+        if (!currentTarget)
+            return;
+
+        var value = currentEditor.getValue();
+        var nextEditable = currentTarget;
+        do
+        {
+            nextEditable = !value && currentGroup
+                ? getNextOutsider(nextEditable, currentGroup)
+                : getNextByClass(nextEditable, "editable");
+        }
+        while (nextEditable && !nextEditable.offsetHeight);
+
+        this.setEditTarget(nextEditable);
+    },
+
+    tabPreviousEditor: function()
+    {
+        if (!currentTarget)
+            return;
+
+        var value = currentEditor.getValue();
+        var prevEditable = currentTarget;
+        do
+        {
+            prevEditable = !value && currentGroup
+                ? getPreviousOutsider(prevEditable, currentGroup)
+                : getPreviousByClass(prevEditable, "editable");
+        }
+        while (prevEditable && !prevEditable.offsetHeight);
+
+        this.setEditTarget(prevEditable);
+    },
+
+    insertRow: function(relative, insertWhere)
+    {
+        var group =
+            relative || getAncestorByClass(currentTarget, "editGroup") || currentTarget;
+        var value = this.stopEditing();
+
+        currentPanel = Firebug.getElementPanel(group);
+
+        currentEditor = currentPanel.getEditor(group, value);
+        if (!currentEditor)
+            currentEditor = getDefaultEditor(currentPanel);
+
+        currentGroup = currentEditor.insertNewRow(group, insertWhere);
+        if (!currentGroup)
+            return;
+
+        var editable = hasClass(currentGroup, "editable")
+            ? currentGroup
+            : getNextByClass(currentGroup, "editable");
+
+        if (editable)
+            this.setEditTarget(editable);
+    },
+
+    insertRowForObject: function(relative)
+    {
+        var container = getAncestorByClass(relative, "insertInto");
+        if (container)
+        {
+            relative = getChildByClass(container, "insertBefore");
+            if (relative)
+                this.insertRow(relative, "before");
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    attachListeners: function(editor, context)
+    {
+        var win = isIE ?
+                currentTarget.ownerDocument.parentWindow :
+                currentTarget.ownerDocument.defaultView;
+
+        addEvent(win, "resize", this.onResize);
+        addEvent(win, "blur", this.onBlur);
+
+        var chrome = Firebug.chrome;
+
+        this.listeners = [
+            chrome.keyCodeListen("ESCAPE", null, bind(this.cancelEditing, this))
+        ];
+
+        if (editor.arrowCompletion)
+        {
+            this.listeners.push(
+                chrome.keyCodeListen("UP", null, bindFixed(editor.completeValue, editor, -1)),
+                chrome.keyCodeListen("DOWN", null, bindFixed(editor.completeValue, editor, 1)),
+                chrome.keyCodeListen("PAGE_UP", null, bindFixed(editor.completeValue, editor, -pageAmount)),
+                chrome.keyCodeListen("PAGE_DOWN", null, bindFixed(editor.completeValue, editor, pageAmount))
+            );
+        }
+
+        if (currentEditor.tabNavigation)
+        {
+            this.listeners.push(
+                chrome.keyCodeListen("RETURN", null, bind(this.tabNextEditor, this)),
+                chrome.keyCodeListen("RETURN", isControl, bind(this.insertRow, this, null, "after")),
+                chrome.keyCodeListen("TAB", null, bind(this.tabNextEditor, this)),
+                chrome.keyCodeListen("TAB", isShift, bind(this.tabPreviousEditor, this))
+            );
+        }
+        else if (currentEditor.multiLine)
+        {
+            this.listeners.push(
+                chrome.keyCodeListen("TAB", null, insertTab)
+            );
+        }
+        else
+        {
+            this.listeners.push(
+                chrome.keyCodeListen("RETURN", null, bindFixed(this.stopEditing, this))
+            );
+
+            if (currentEditor.tabCompletion)
+            {
+                this.listeners.push(
+                    chrome.keyCodeListen("TAB", null, bind(editor.completeValue, editor, 1)),
+                    chrome.keyCodeListen("TAB", isShift, bind(editor.completeValue, editor, -1))
+                );
+            }
+        }
+    },
+
+    detachListeners: function(editor, context)
+    {
+        if (!this.listeners)
+            return;
+
+        var win = isIE ?
+                currentTarget.ownerDocument.parentWindow :
+                currentTarget.ownerDocument.defaultView;
+
+        removeEvent(win, "resize", this.onResize);
+        removeEvent(win, "blur", this.onBlur);
+
+        var chrome = Firebug.chrome;
+        if (chrome)
+        {
+            for (var i = 0; i < this.listeners.length; ++i)
+                chrome.keyIgnore(this.listeners[i]);
+        }
+
+        delete this.listeners;
+    },
+
+    onResize: function(event)
+    {
+        currentEditor.layout(true);
+    },
+
+    onBlur: function(event)
+    {
+        if (currentEditor.enterOnBlur && isAncestor(event.target, currentEditor.box))
+            this.stopEditing();
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Module
+
+    initialize: function()
+    {
+        Firebug.Module.initialize.apply(this, arguments);
+
+        this.onResize = bindFixed(this.onResize, this);
+        this.onBlur = bind(this.onBlur, this);
+    },
+
+    disable: function()
+    {
+        this.stopEditing();
+    },
+
+    showContext: function(browser, context)
+    {
+        this.stopEditing();
+    },
+
+    showPanel: function(browser, panel)
+    {
+        this.stopEditing();
+    }
+});
+
+// ************************************************************************************************
+// BaseEditor
+
+Firebug.BaseEditor = extend(Firebug.MeasureBox,
+{
+    getValue: function()
+    {
+    },
+
+    setValue: function(value)
+    {
+    },
+
+    show: function(target, panel, value, textSize, targetSize)
+    {
+    },
+
+    hide: function()
+    {
+    },
+
+    layout: function(forceAll)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Support for context menus within inline editors.
+
+    getContextMenuItems: function(target)
+    {
+        var items = [];
+        items.push({label: "Cut", commandID: "cmd_cut"});
+        items.push({label: "Copy", commandID: "cmd_copy"});
+        items.push({label: "Paste", commandID: "cmd_paste"});
+        return items;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Editor Module listeners will get "onBeginEditing" just before this call
+
+    beginEditing: function(target, value)
+    {
+    },
+
+    // Editor Module listeners will get "onSaveEdit" just after this call
+    saveEdit: function(target, value, previousValue)
+    {
+    },
+
+    endEditing: function(target, value, cancel)
+    {
+        // Remove empty groups by default
+        return true;
+    },
+
+    insertNewRow: function(target, insertWhere)
+    {
+    }
+});
+
+// ************************************************************************************************
+// InlineEditor
+
+// basic inline editor attributes
+var inlineEditorAttributes = {
+    "class": "textEditorInner",
+
+    type: "text",
+    spellcheck: "false",
+
+    onkeypress: "$onKeyPress",
+
+    onoverflow: "$onOverflow",
+    oncontextmenu: "$onContextMenu"
+};
+
+// IE does not support the oninput event, so we're using the onkeydown to signalize
+// the relevant keyboard events, and the onpropertychange to actually handle the
+// input event, which should happen after the onkeydown event is fired and after the
+// value of the input is updated, but before the onkeyup and before the input (with the
+// new value) is rendered
+if (isIE)
+{
+    inlineEditorAttributes.onpropertychange = "$onInput";
+    inlineEditorAttributes.onkeydown = "$onKeyDown";
+}
+// for other browsers we use the oninput event
+else
+{
+    inlineEditorAttributes.oninput = "$onInput";
+}
+
+Firebug.InlineEditor = function(doc)
+{
+    this.initializeInline(doc);
+};
+
+Firebug.InlineEditor.prototype = domplate(Firebug.BaseEditor,
+{
+    enterOnBlur: true,
+    outerMargin: 8,
+    shadowExpand: 7,
+
+    tag:
+        DIV({"class": "inlineEditor"},
+            DIV({"class": "textEditorTop1"},
+                DIV({"class": "textEditorTop2"})
+            ),
+            DIV({"class": "textEditorInner1"},
+                DIV({"class": "textEditorInner2"},
+                    INPUT(
+                        inlineEditorAttributes
+                    )
+                )
+            ),
+            DIV({"class": "textEditorBottom1"},
+                DIV({"class": "textEditorBottom2"})
+            )
+        ),
+
+    inputTag :
+        INPUT({"class": "textEditorInner", type: "text",
+            /*oninput: "$onInput",*/ onkeypress: "$onKeyPress", onoverflow: "$onOverflow"}
+        ),
+
+    expanderTag:
+        IMG({"class": "inlineExpander", src: "blank.gif"}),
+
+    initialize: function()
+    {
+        this.fixedWidth = false;
+        this.completeAsYouType = true;
+        this.tabNavigation = true;
+        this.multiLine = false;
+        this.tabCompletion = false;
+        this.arrowCompletion = true;
+        this.noWrap = true;
+        this.numeric = false;
+    },
+
+    destroy: function()
+    {
+        this.destroyInput();
+    },
+
+    initializeInline: function(doc)
+    {
+        if (FBTrace.DBG_EDITOR)
+            FBTrace.sysout("Firebug.InlineEditor initializeInline()");
+
+        //this.box = this.tag.replace({}, doc, this);
+        this.box = this.tag.append({}, doc.body, this);
+
+        //this.input = this.box.childNodes[1].firstChild.firstChild;  // XXXjjb childNode[1] required
+        this.input = this.box.getElementsByTagName("input")[0];
+
+        if (isIElt8)
+        {
+            this.input.style.top = "-8px";
+        }
+
+        this.expander = this.expanderTag.replace({}, doc, this);
+        this.initialize();
+    },
+
+    destroyInput: function()
+    {
+        // XXXjoe Need to remove input/keypress handlers to avoid leaks
+    },
+
+    getValue: function()
+    {
+        return this.input.value;
+    },
+
+    setValue: function(value)
+    {
+        // It's only a one-line editor, so new lines shouldn't be allowed
+        return this.input.value = stripNewLines(value);
+    },
+
+    show: function(target, panel, value, targetSize)
+    {
+        //dispatch([Firebug.A11yModel], "onInlineEditorShow", [panel, this]);
+        this.target = target;
+        this.panel = panel;
+
+        this.targetSize = targetSize;
+
+        // TODO: xxxpedro editor
+        //this.targetOffset = getClientOffset(target);
+
+        // Some browsers (IE, Google Chrome and Safari) will have problem trying to get the
+        // offset values of invisible elements, or empty elements. So, in order to get the
+        // correct values, we temporary inject a character in the innerHTML of the empty element,
+        // then we get the offset values, and next, we restore the original innerHTML value.
+        var innerHTML = target.innerHTML;
+        var isEmptyElement = !innerHTML;
+        if (isEmptyElement)
+            target.innerHTML = ".";
+
+        // Get the position of the target element (that is about to be edited)
+        this.targetOffset =
+        {
+            x: target.offsetLeft,
+            y: target.offsetTop
+        };
+
+        // Restore the original innerHTML value of the empty element
+        if (isEmptyElement)
+            target.innerHTML = innerHTML;
+
+        this.originalClassName = this.box.className;
+
+        var classNames = target.className.split(" ");
+        for (var i = 0; i < classNames.length; ++i)
+            setClass(this.box, "editor-" + classNames[i]);
+
+        // Make the editor match the target's font style
+        copyTextStyles(target, this.box);
+
+        this.setValue(value);
+
+        if (this.fixedWidth)
+            this.updateLayout(true);
+        else
+        {
+            this.startMeasuring(target);
+            this.textSize = this.measureInputText(value);
+
+            // Correct the height of the box to make the funky CSS drop-shadow line up
+            var parent = this.input.parentNode;
+            if (hasClass(parent, "textEditorInner2"))
+            {
+                var yDiff = this.textSize.height - this.shadowExpand;
+
+                // IE6 height offset
+                if (isIE6)
+                    yDiff -= 2;
+
+                parent.style.height = yDiff + "px";
+                parent.parentNode.style.height = yDiff + "px";
+            }
+
+            this.updateLayout(true);
+        }
+
+        this.getAutoCompleter().reset();
+
+        if (isIElt8)
+            panel.panelNode.appendChild(this.box);
+        else
+            target.offsetParent.appendChild(this.box);
+
+        //console.log(target);
+        //this.input.select(); // it's called bellow, with setTimeout
+
+        if (isIE)
+        {
+            // reset input style
+            this.input.style.fontFamily = "Monospace";
+            this.input.style.fontSize = "11px";
+        }
+
+        // Insert the "expander" to cover the target element with white space
+        if (!this.fixedWidth)
+        {
+            copyBoxStyles(target, this.expander);
+
+            target.parentNode.replaceChild(this.expander, target);
+            collapse(target, true);
+            this.expander.parentNode.insertBefore(target, this.expander);
+        }
+
+        //TODO: xxxpedro
+        //scrollIntoCenterView(this.box, null, true);
+
+        // Display the editor after change its size and position to avoid flickering
+        this.box.style.display = "block";
+
+        // we need to call input.focus() and input.select() with a timeout,
+        // otherwise it won't work on all browsers due to timing issues
+        var self = this;
+        setTimeout(function(){
+            self.input.focus();
+            self.input.select();
+        },0);
+    },
+
+    hide: function()
+    {
+        this.box.className = this.originalClassName;
+
+        if (!this.fixedWidth)
+        {
+            this.stopMeasuring();
+
+            collapse(this.target, false);
+
+            if (this.expander.parentNode)
+                this.expander.parentNode.removeChild(this.expander);
+        }
+
+        if (this.box.parentNode)
+        {
+            ///setSelectionRange(this.input, 0, 0);
+            this.input.blur();
+
+            this.box.parentNode.removeChild(this.box);
+        }
+
+        delete this.target;
+        delete this.panel;
+    },
+
+    layout: function(forceAll)
+    {
+        if (!this.fixedWidth)
+            this.textSize = this.measureInputText(this.input.value);
+
+        if (forceAll)
+            this.targetOffset = getClientOffset(this.expander);
+
+        this.updateLayout(false, forceAll);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    beginEditing: function(target, value)
+    {
+    },
+
+    saveEdit: function(target, value, previousValue)
+    {
+    },
+
+    endEditing: function(target, value, cancel)
+    {
+        // Remove empty groups by default
+        return true;
+    },
+
+    insertNewRow: function(target, insertWhere)
+    {
+    },
+
+    advanceToNext: function(target, charCode)
+    {
+        return false;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getAutoCompleteRange: function(value, offset)
+    {
+    },
+
+    getAutoCompleteList: function(preExpr, expr, postExpr)
+    {
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getAutoCompleter: function()
+    {
+        if (!this.autoCompleter)
+        {
+            this.autoCompleter = new Firebug.AutoCompleter(null,
+                bind(this.getAutoCompleteRange, this), bind(this.getAutoCompleteList, this),
+                true, false);
+        }
+
+        return this.autoCompleter;
+    },
+
+    completeValue: function(amt)
+    {
+        //console.log("completeValue");
+
+        var selectRangeCallback = this.getAutoCompleter().complete(currentPanel.context, this.input, true, amt < 0);
+
+        if (selectRangeCallback)
+        {
+            Firebug.Editor.update(true);
+
+            // We need to select the editor text after calling update in Safari/Chrome,
+            // otherwise the text won't be selected
+            if (isSafari)
+                setTimeout(selectRangeCallback,0);
+            else
+                selectRangeCallback();
+        }
+        else
+            this.incrementValue(amt);
+    },
+
+    incrementValue: function(amt)
+    {
+        var value = this.input.value;
+
+        // TODO: xxxpedro editor
+        if (isIE)
+            var start = getInputSelectionStart(this.input), end = start;
+        else
+            var start = this.input.selectionStart, end = this.input.selectionEnd;
+
+        //debugger;
+        var range = this.getAutoCompleteRange(value, start);
+        if (!range || range.type != "int")
+            range = {start: 0, end: value.length-1};
+
+        var expr = value.substr(range.start, range.end-range.start+1);
+        preExpr = value.substr(0, range.start);
+        postExpr = value.substr(range.end+1);
+
+        // See if the value is an integer, and if so increment it
+        var intValue = parseInt(expr);
+        if (!!intValue || intValue == 0)
+        {
+            var m = /\d+/.exec(expr);
+            var digitPost = expr.substr(m.index+m[0].length);
+
+            var completion = intValue-amt;
+            this.input.value = preExpr + completion + digitPost + postExpr;
+
+            setSelectionRange(this.input, start, end);
+
+            Firebug.Editor.update(true);
+
+            return true;
+        }
+        else
+            return false;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onKeyPress: function(event)
+    {
+        //console.log("onKeyPress", event);
+        if (event.keyCode == 27 && !this.completeAsYouType)
+        {
+            var reverted = this.getAutoCompleter().revert(this.input);
+            if (reverted)
+                cancelEvent(event);
+        }
+        else if (event.charCode && this.advanceToNext(this.target, event.charCode))
+        {
+            Firebug.Editor.tabNextEditor();
+            cancelEvent(event);
+        }
+        else
+        {
+            if (this.numeric && event.charCode && (event.charCode < 48 || event.charCode > 57)
+                && event.charCode != 45 && event.charCode != 46)
+                FBL.cancelEvent(event);
+            else
+            {
+                // If the user backspaces, don't autocomplete after the upcoming input event
+                this.ignoreNextInput = event.keyCode == 8;
+            }
+        }
+    },
+
+    onOverflow: function()
+    {
+        this.updateLayout(false, false, 3);
+    },
+
+    onKeyDown: function(event)
+    {
+        //console.log("onKeyDown", event.keyCode);
+        if (event.keyCode > 46 || event.keyCode == 32 || event.keyCode == 8)
+        {
+            this.keyDownPressed = true;
+        }
+    },
+
+    onInput: function(event)
+    {
+        //debugger;
+
+        // skip not relevant onpropertychange calls on IE
+        if (isIE)
+        {
+            if (event.propertyName != "value" || !isVisible(this.input) || !this.keyDownPressed)
+                return;
+
+            this.keyDownPressed = false;
+        }
+
+        //console.log("onInput", event);
+        //console.trace();
+
+        var selectRangeCallback;
+
+        if (this.ignoreNextInput)
+        {
+            this.ignoreNextInput = false;
+            this.getAutoCompleter().reset();
+        }
+        else if (this.completeAsYouType)
+            selectRangeCallback = this.getAutoCompleter().complete(currentPanel.context, this.input, false);
+        else
+            this.getAutoCompleter().reset();
+
+        Firebug.Editor.update();
+
+        if (selectRangeCallback)
+        {
+            // We need to select the editor text after calling update in Safari/Chrome,
+            // otherwise the text won't be selected
+            if (isSafari)
+                setTimeout(selectRangeCallback,0);
+            else
+                selectRangeCallback();
+        }
+    },
+
+    onContextMenu: function(event)
+    {
+        cancelEvent(event);
+
+        var popup = $("fbInlineEditorPopup");
+        FBL.eraseNode(popup);
+
+        var target = event.target || event.srcElement;
+        var menu = this.getContextMenuItems(target);
+        if (menu)
+        {
+            for (var i = 0; i < menu.length; ++i)
+                FBL.createMenuItem(popup, menu[i]);
+        }
+
+        if (!popup.firstChild)
+            return false;
+
+        popup.openPopupAtScreen(event.screenX, event.screenY, true);
+        return true;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    updateLayout: function(initial, forceAll, extraWidth)
+    {
+        if (this.fixedWidth)
+        {
+            this.box.style.left = (this.targetOffset.x) + "px";
+            this.box.style.top = (this.targetOffset.y) + "px";
+
+            var w = this.target.offsetWidth;
+            var h = this.target.offsetHeight;
+            this.input.style.width = w + "px";
+            this.input.style.height = (h-3) + "px";
+        }
+        else
+        {
+            if (initial || forceAll)
+            {
+                this.box.style.left = this.targetOffset.x + "px";
+                this.box.style.top = this.targetOffset.y + "px";
+            }
+
+            var approxTextWidth = this.textSize.width;
+            var maxWidth = (currentPanel.panelNode.scrollWidth - this.targetOffset.x)
+                - this.outerMargin;
+
+            var wrapped = initial
+                ? this.noWrap && this.targetSize.height > this.textSize.height+3
+                : this.noWrap && approxTextWidth > maxWidth;
+
+            if (wrapped)
+            {
+                var style = isIE ?
+                        this.target.currentStyle :
+                        this.target.ownerDocument.defaultView.getComputedStyle(this.target, "");
+
+                targetMargin = parseInt(style.marginLeft) + parseInt(style.marginRight);
+
+                // Make the width fit the remaining x-space from the offset to the far right
+                approxTextWidth = maxWidth - targetMargin;
+
+                this.input.style.width = "100%";
+                this.box.style.width = approxTextWidth + "px";
+            }
+            else
+            {
+                // Make the input one character wider than the text value so that
+                // typing does not ever cause the textbox to scroll
+                var charWidth = this.measureInputText('m').width;
+
+                // Sometimes we need to make the editor a little wider, specifically when
+                // an overflow happens, otherwise it will scroll off some text on the left
+                if (extraWidth)
+                    charWidth *= extraWidth;
+
+                var inputWidth = approxTextWidth + charWidth;
+
+                if (initial)
+                {
+                    if (isIE)
+                    {
+                        // TODO: xxxpedro
+                        var xDiff = 13;
+                        this.box.style.width = (inputWidth + xDiff) + "px";
+                    }
+                    else
+                        this.box.style.width = "auto";
+                }
+                else
+                {
+                    // TODO: xxxpedro
+                    var xDiff = isIE ? 13: this.box.scrollWidth - this.input.offsetWidth;
+                    this.box.style.width = (inputWidth + xDiff) + "px";
+                }
+
+                this.input.style.width = inputWidth + "px";
+            }
+
+            this.expander.style.width = approxTextWidth + "px";
+            this.expander.style.height = Math.max(this.textSize.height-3,0) + "px";
+        }
+
+        if (forceAll)
+            scrollIntoCenterView(this.box, null, true);
+    }
+});
+
+// ************************************************************************************************
+// Autocompletion
+
+Firebug.AutoCompleter = function(getExprOffset, getRange, evaluator, selectMode, caseSensitive)
+{
+    var candidates = null;
+    var originalValue = null;
+    var originalOffset = -1;
+    var lastExpr = null;
+    var lastOffset = -1;
+    var exprOffset = 0;
+    var lastIndex = 0;
+    var preParsed = null;
+    var preExpr = null;
+    var postExpr = null;
+
+    this.revert = function(textBox)
+    {
+        if (originalOffset != -1)
+        {
+            textBox.value = originalValue;
+
+            setSelectionRange(textBox, originalOffset, originalOffset);
+
+            this.reset();
+            return true;
+        }
+        else
+        {
+            this.reset();
+            return false;
+        }
+    };
+
+    this.reset = function()
+    {
+        candidates = null;
+        originalValue = null;
+        originalOffset = -1;
+        lastExpr = null;
+        lastOffset = 0;
+        exprOffset = 0;
+    };
+
+    this.complete = function(context, textBox, cycle, reverse)
+    {
+        //console.log("complete", context, textBox, cycle, reverse);
+        // TODO: xxxpedro important port to firebug (variable leak)
+        //var value = lastValue = textBox.value;
+        var value = textBox.value;
+
+        //var offset = textBox.selectionStart;
+        var offset = getInputSelectionStart(textBox);
+
+        // The result of selectionStart() in Safari/Chrome is 1 unit less than the result
+        // in Firefox. Therefore, we need to manually adjust the value here.
+        if (isSafari && !cycle && offset >= 0) offset++;
+
+        if (!selectMode && originalOffset != -1)
+            offset = originalOffset;
+
+        if (!candidates || !cycle || offset != lastOffset)
+        {
+            originalOffset = offset;
+            originalValue = value;
+
+            // Find the part of the string that will be parsed
+            var parseStart = getExprOffset ? getExprOffset(value, offset, context) : 0;
+            preParsed = value.substr(0, parseStart);
+            var parsed = value.substr(parseStart);
+
+            // Find the part of the string that is being completed
+            var range = getRange ? getRange(parsed, offset-parseStart, context) : null;
+            if (!range)
+                range = {start: 0, end: parsed.length-1 };
+
+            var expr = parsed.substr(range.start, range.end-range.start+1);
+            preExpr = parsed.substr(0, range.start);
+            postExpr = parsed.substr(range.end+1);
+            exprOffset = parseStart + range.start;
+
+            if (!cycle)
+            {
+                if (!expr)
+                    return;
+                else if (lastExpr && lastExpr.indexOf(expr) != 0)
+                {
+                    candidates = null;
+                }
+                else if (lastExpr && lastExpr.length >= expr.length)
+                {
+                    candidates = null;
+                    lastExpr = expr;
+                    return;
+                }
+            }
+
+            lastExpr = expr;
+            lastOffset = offset;
+
+            var searchExpr;
+
+            // Check if the cursor is at the very right edge of the expression, or
+            // somewhere in the middle of it
+            if (expr && offset != parseStart+range.end+1)
+            {
+                if (cycle)
+                {
+                    // We are in the middle of the expression, but we can
+                    // complete by cycling to the next item in the values
+                    // list after the expression
+                    offset = range.start;
+                    searchExpr = expr;
+                    expr = "";
+                }
+                else
+                {
+                    // We can't complete unless we are at the ridge edge
+                    return;
+                }
+            }
+
+            var values = evaluator(preExpr, expr, postExpr, context);
+            if (!values)
+                return;
+
+            if (expr)
+            {
+                // Filter the list of values to those which begin with expr. We
+                // will then go on to complete the first value in the resulting list
+                candidates = [];
+
+                if (caseSensitive)
+                {
+                    for (var i = 0; i < values.length; ++i)
+                    {
+                        var name = values[i];
+                        if (name.indexOf && name.indexOf(expr) == 0)
+                            candidates.push(name);
+                    }
+                }
+                else
+                {
+                    var lowerExpr = caseSensitive ? expr : expr.toLowerCase();
+                    for (var i = 0; i < values.length; ++i)
+                    {
+                        var name = values[i];
+                        if (name.indexOf && name.toLowerCase().indexOf(lowerExpr) == 0)
+                            candidates.push(name);
+                    }
+                }
+
+                lastIndex = reverse ? candidates.length-1 : 0;
+            }
+            else if (searchExpr)
+            {
+                var searchIndex = -1;
+
+                // Find the first instance of searchExpr in the values list. We
+                // will then complete the string that is found
+                if (caseSensitive)
+                {
+                    searchIndex = values.indexOf(expr);
+                }
+                else
+                {
+                    var lowerExpr = searchExpr.toLowerCase();
+                    for (var i = 0; i < values.length; ++i)
+                    {
+                        var name = values[i];
+                        if (name && name.toLowerCase().indexOf(lowerExpr) == 0)
+                        {
+                            searchIndex = i;
+                            break;
+                        }
+                    }
+                }
+
+                // Nothing found, so there's nothing to complete to
+                if (searchIndex == -1)
+                    return this.reset();
+
+                expr = searchExpr;
+                candidates = cloneArray(values);
+                lastIndex = searchIndex;
+            }
+            else
+            {
+                expr = "";
+                candidates = [];
+                for (var i = 0; i < values.length; ++i)
+                {
+                    if (values[i].substr)
+                        candidates.push(values[i]);
+                }
+                lastIndex = -1;
+            }
+        }
+
+        if (cycle)
+        {
+            expr = lastExpr;
+            lastIndex += reverse ? -1 : 1;
+        }
+
+        if (!candidates.length)
+            return;
+
+        if (lastIndex >= candidates.length)
+            lastIndex = 0;
+        else if (lastIndex < 0)
+            lastIndex = candidates.length-1;
+
+        var completion = candidates[lastIndex];
+        var preCompletion = expr.substr(0, offset-exprOffset);
+        var postCompletion = completion.substr(offset-exprOffset);
+
+        textBox.value = preParsed + preExpr + preCompletion + postCompletion + postExpr;
+        var offsetEnd = preParsed.length + preExpr.length + completion.length;
+
+        // TODO: xxxpedro remove the following commented code, if the lib.setSelectionRange()
+        // is working well.
+        /*
+        if (textBox.setSelectionRange)
+        {
+            // we must select the range with a timeout, otherwise the text won't
+            // be properly selected (because after this function executes, the editor's
+            // input will be resized to fit the whole text)
+            setTimeout(function(){
+                if (selectMode)
+                    textBox.setSelectionRange(offset, offsetEnd);
+                else
+                    textBox.setSelectionRange(offsetEnd, offsetEnd);
+            },0);
+        }
+        /**/
+
+        // we must select the range with a timeout, otherwise the text won't
+        // be properly selected (because after this function executes, the editor's
+        // input will be resized to fit the whole text)
+        /*
+        setTimeout(function(){
+            if (selectMode)
+                setSelectionRange(textBox, offset, offsetEnd);
+            else
+                setSelectionRange(textBox, offsetEnd, offsetEnd);
+        },0);
+
+        return true;
+        /**/
+
+        // The editor text should be selected only after calling the editor.update()
+        // in Safari/Chrome, otherwise the text won't be selected. So, we're returning
+        // a function to be called later (in the proper time for all browsers).
+        //
+        // TODO: xxxpedro see if we can move the editor.update() calls to here, and avoid
+        // returning a closure. the complete() function seems to be called only twice in
+        // editor.js. See if this function is called anywhere else (like css.js for example).
+        return function(){
+            //console.log("autocomplete ", textBox, offset, offsetEnd);
+
+            if (selectMode)
+                setSelectionRange(textBox, offset, offsetEnd);
+            else
+                setSelectionRange(textBox, offsetEnd, offsetEnd);
+        };
+        /**/
+    };
+};
+
+// ************************************************************************************************
+// Local Helpers
+
+var getDefaultEditor = function getDefaultEditor(panel)
+{
+    if (!defaultEditor)
+    {
+        var doc = panel.document;
+        defaultEditor = new Firebug.InlineEditor(doc);
+    }
+
+    return defaultEditor;
+}
+
+/**
+ * An outsider is the first element matching the stepper element that
+ * is not an child of group. Elements tagged with insertBefore or insertAfter
+ * classes are also excluded from these results unless they are the sibling
+ * of group, relative to group's parent editGroup. This allows for the proper insertion
+ * rows when groups are nested.
+ */
+var getOutsider = function getOutsider(element, group, stepper)
+{
+    var parentGroup = getAncestorByClass(group.parentNode, "editGroup");
+    var next;
+    do
+    {
+        next = stepper(next || element);
+    }
+    while (isAncestor(next, group) || isGroupInsert(next, parentGroup));
+
+    return next;
+}
+
+var isGroupInsert = function isGroupInsert(next, group)
+{
+    return (!group || isAncestor(next, group))
+        && (hasClass(next, "insertBefore") || hasClass(next, "insertAfter"));
+}
+
+var getNextOutsider = function getNextOutsider(element, group)
+{
+    return getOutsider(element, group, bind(getNextByClass, FBL, "editable"));
+}
+
+var getPreviousOutsider = function getPreviousOutsider(element, group)
+{
+    return getOutsider(element, group, bind(getPreviousByClass, FBL, "editable"));
+}
+
+var getInlineParent = function getInlineParent(element)
+{
+    var lastInline = element;
+    for (; element; element = element.parentNode)
+    {
+        //var s = element.ownerDocument.defaultView.getComputedStyle(element, "");
+        var s = isIE ?
+                element.currentStyle :
+                element.ownerDocument.defaultView.getComputedStyle(element, "");
+
+        if (s.display != "inline")
+            return lastInline;
+        else
+            lastInline = element;
+    }
+    return null;
+}
+
+var insertTab = function insertTab()
+{
+    insertTextIntoElement(currentEditor.input, Firebug.Editor.tabCharacter);
+}
+
+// ************************************************************************************************
+
+Firebug.registerModule(Firebug.Editor);
+
+// ************************************************************************************************
+
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+if (Env.Options.disableXHRListener)
+    return;
+
+// ************************************************************************************************
+// XHRSpy
+
+var XHRSpy = function()
+{
+    this.requestHeaders = [];
+    this.responseHeaders = [];
+};
+
+XHRSpy.prototype =
+{
+    method: null,
+    url: null,
+    async: null,
+
+    xhrRequest: null,
+
+    href: null,
+
+    loaded: false,
+
+    logRow: null,
+
+    responseText: null,
+
+    requestHeaders: null,
+    responseHeaders: null,
+
+    sourceLink: null, // {href:"file.html", line: 22}
+
+    getURL: function()
+    {
+        return this.href;
+    }
+};
+
+// ************************************************************************************************
+// XMLHttpRequestWrapper
+
+var XMLHttpRequestWrapper = function(activeXObject)
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // XMLHttpRequestWrapper internal variables
+
+    var xhrRequest = typeof activeXObject != "undefined" ?
+                activeXObject :
+                new _XMLHttpRequest(),
+
+        spy = new XHRSpy(),
+
+        self = this,
+
+        reqType,
+        reqUrl,
+        reqStartTS;
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // XMLHttpRequestWrapper internal methods
+
+    var updateSelfPropertiesIgnore = {
+        abort: 1,
+        channel: 1,
+        getAllResponseHeaders: 1,
+        getInterface: 1,
+        getResponseHeader: 1,
+        mozBackgroundRequest: 1,
+        multipart: 1,
+        onreadystatechange: 1,
+        open: 1,
+        send: 1,
+        setRequestHeader: 1
+    };
+
+    var updateSelfProperties = function()
+    {
+        if (supportsXHRIterator)
+        {
+            for (var propName in xhrRequest)
+            {
+                if (propName in updateSelfPropertiesIgnore)
+                    continue;
+
+                try
+                {
+                    var propValue = xhrRequest[propName];
+
+                    if (propValue && !isFunction(propValue))
+                        self[propName] = propValue;
+                }
+                catch(E)
+                {
+                    //console.log(propName, E.message);
+                }
+            }
+        }
+        else
+        {
+            // will fail to read these xhrRequest properties if the request is not completed
+            if (xhrRequest.readyState == 4)
+            {
+                self.status = xhrRequest.status;
+                self.statusText = xhrRequest.statusText;
+                self.responseText = xhrRequest.responseText;
+                self.responseXML = xhrRequest.responseXML;
+            }
+        }
+    };
+
+    var updateXHRPropertiesIgnore = {
+        channel: 1,
+        onreadystatechange: 1,
+        readyState: 1,
+        responseBody: 1,
+        responseText: 1,
+        responseXML: 1,
+        status: 1,
+        statusText: 1,
+        upload: 1
+    };
+
+    var updateXHRProperties = function()
+    {
+        for (var propName in self)
+        {
+            if (propName in updateXHRPropertiesIgnore)
+                continue;
+
+            try
+            {
+                var propValue = self[propName];
+
+                if (propValue && !xhrRequest[propName])
+                {
+                    xhrRequest[propName] = propValue;
+                }
+            }
+            catch(E)
+            {
+                //console.log(propName, E.message);
+            }
+        }
+    };
+
+    var logXHR = function()
+    {
+        var row = Firebug.Console.log(spy, null, "spy", Firebug.Spy.XHR);
+
+        if (row)
+        {
+            setClass(row, "loading");
+            spy.logRow = row;
+        }
+    };
+
+    var finishXHR = function()
+    {
+        var duration = new Date().getTime() - reqStartTS;
+        var success = xhrRequest.status == 200;
+
+        var responseHeadersText = xhrRequest.getAllResponseHeaders();
+        var responses = responseHeadersText ? responseHeadersText.split(/[\n\r]/) : [];
+        var reHeader = /^(\S+):\s*(.*)/;
+
+        for (var i=0, l=responses.length; i<l; i++)
+        {
+            var text = responses[i];
+            var match = text.match(reHeader);
+
+            if (match)
+            {
+                var name = match[1];
+                var value = match[2];
+
+                // update the spy mimeType property so we can detect when to show
+                // custom response viewers (such as HTML, XML or JSON viewer)
+                if (name == "Content-Type")
+                    spy.mimeType = value;
+
+                /*
+                if (name == "Last Modified")
+                {
+                    if (!spy.cacheEntry)
+                        spy.cacheEntry = [];
+
+                    spy.cacheEntry.push({
+                       name: [name],
+                       value: [value]
+                    });
+                }
+                /**/
+
+                spy.responseHeaders.push({
+                   name: [name],
+                   value: [value]
+                });
+            }
+        }
+
+        with({
+            row: spy.logRow,
+            status: xhrRequest.status == 0 ?
+                        // if xhrRequest.status == 0 then accessing xhrRequest.statusText
+                        // will cause an error, so we must handle this case (Issue 3504)
+                        "" : xhrRequest.status + " " + xhrRequest.statusText,
+            time: duration,
+            success: success
+        })
+        {
+            setTimeout(function(){
+
+                spy.responseText = xhrRequest.responseText;
+
+                // update row information to avoid "ethernal spinning gif" bug in IE
+                row = row || spy.logRow;
+
+                // if chrome document is not loaded, there will be no row yet, so just ignore
+                if (!row) return;
+
+                // update the XHR representation data
+                handleRequestStatus(success, status, time);
+
+            },200);
+        }
+
+        spy.loaded = true;
+        /*
+        // commented because they are being updated by the updateSelfProperties() function
+        self.status = xhrRequest.status;
+        self.statusText = xhrRequest.statusText;
+        self.responseText = xhrRequest.responseText;
+        self.responseXML = xhrRequest.responseXML;
+        /**/
+        updateSelfProperties();
+    };
+
+    var handleStateChange = function()
+    {
+        //Firebug.Console.log(["onreadystatechange", xhrRequest.readyState, xhrRequest.readyState == 4 && xhrRequest.status]);
+
+        self.readyState = xhrRequest.readyState;
+
+        if (xhrRequest.readyState == 4)
+        {
+            finishXHR();
+
+            xhrRequest.onreadystatechange = function(){};
+        }
+
+        //Firebug.Console.log(spy.url + ": " + xhrRequest.readyState);
+
+        self.onreadystatechange();
+    };
+
+    // update the XHR representation data
+    var handleRequestStatus = function(success, status, time)
+    {
+        var row = spy.logRow;
+        FBL.removeClass(row, "loading");
+
+        if (!success)
+            FBL.setClass(row, "error");
+
+        var item = FBL.$$(".spyStatus", row)[0];
+        item.innerHTML = status;
+
+        if (time)
+        {
+            var item = FBL.$$(".spyTime", row)[0];
+            item.innerHTML = time + "ms";
+        }
+    };
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // XMLHttpRequestWrapper public properties and handlers
+
+    this.readyState = 0;
+
+    this.onreadystatechange = function(){};
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // XMLHttpRequestWrapper public methods
+
+    this.open = function(method, url, async, user, password)
+    {
+        //Firebug.Console.log("xhrRequest open");
+
+        updateSelfProperties();
+
+        if (spy.loaded)
+            spy = new XHRSpy();
+
+        spy.method = method;
+        spy.url = url;
+        spy.async = async;
+        spy.href = url;
+        spy.xhrRequest = xhrRequest;
+        spy.urlParams = parseURLParamsArray(url);
+
+        try
+        {
+            // xhrRequest.open.apply may not be available in IE
+            if (supportsApply)
+                xhrRequest.open.apply(xhrRequest, arguments);
+            else
+                xhrRequest.open(method, url, async, user, password);
+        }
+        catch(e)
+        {
+        }
+
+        xhrRequest.onreadystatechange = handleStateChange;
+
+    };
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    this.send = function(data)
+    {
+        //Firebug.Console.log("xhrRequest send");
+        spy.data = data;
+
+        reqStartTS = new Date().getTime();
+
+        updateXHRProperties();
+
+        try
+        {
+            xhrRequest.send(data);
+        }
+        catch(e)
+        {
+            // TODO: xxxpedro XHR throws or not?
+            //throw e;
+        }
+        finally
+        {
+            logXHR();
+
+            if (!spy.async)
+            {
+                self.readyState = xhrRequest.readyState;
+
+                // sometimes an error happens when calling finishXHR()
+                // Issue 3422: Firebug Lite breaks Google Instant Search
+                try
+                {
+                    finishXHR();
+                }
+                catch(E)
+                {
+                }
+            }
+        }
+    };
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    this.setRequestHeader = function(header, value)
+    {
+        spy.requestHeaders.push({name: [header], value: [value]});
+        return xhrRequest.setRequestHeader(header, value);
+    };
+
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    this.abort = function()
+    {
+        xhrRequest.abort();
+        updateSelfProperties();
+        handleRequestStatus(false, "Aborted");
+    };
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    this.getResponseHeader = function(header)
+    {
+        return xhrRequest.getResponseHeader(header);
+    };
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    this.getAllResponseHeaders = function()
+    {
+        return xhrRequest.getAllResponseHeaders();
+    };
+
+    /**/
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Clone XHR object
+
+    // xhrRequest.open.apply not available in IE and will throw an error in
+    // IE6 by simply reading xhrRequest.open so we must sniff it
+    var supportsApply = !isIE6 &&
+            xhrRequest &&
+            xhrRequest.open &&
+            typeof xhrRequest.open.apply != "undefined";
+
+    var numberOfXHRProperties = 0;
+    for (var propName in xhrRequest)
+    {
+        numberOfXHRProperties++;
+
+        if (propName in updateSelfPropertiesIgnore)
+            continue;
+
+        try
+        {
+            var propValue = xhrRequest[propName];
+
+            if (isFunction(propValue))
+            {
+                if (typeof self[propName] == "undefined")
+                {
+                    this[propName] = (function(name, xhr){
+
+                        return supportsApply ?
+                            // if the browser supports apply
+                            function()
+                            {
+                                return xhr[name].apply(xhr, arguments);
+                            }
+                            :
+                            function(a,b,c,d,e)
+                            {
+                                return xhr[name](a,b,c,d,e);
+                            };
+
+                    })(propName, xhrRequest);
+                }
+            }
+            else
+                this[propName] = propValue;
+        }
+        catch(E)
+        {
+            //console.log(propName, E.message);
+        }
+    }
+
+    // IE6 does not support for (var prop in XHR)
+    var supportsXHRIterator = numberOfXHRProperties > 0;
+
+    /**/
+
+    return this;
+};
+
+// ************************************************************************************************
+// ActiveXObject Wrapper (IE6 only)
+
+var _ActiveXObject;
+var isIE6 =  /msie 6/i.test(navigator.appVersion);
+
+if (isIE6)
+{
+    _ActiveXObject = window.ActiveXObject;
+
+    var xhrObjects = " MSXML2.XMLHTTP.5.0 MSXML2.XMLHTTP.4.0 MSXML2.XMLHTTP.3.0 MSXML2.XMLHTTP Microsoft.XMLHTTP ";
+
+    window.ActiveXObject = function(name)
+    {
+        var error = null;
+
+        try
+        {
+            var activeXObject = new _ActiveXObject(name);
+        }
+        catch(e)
+        {
+            error = e;
+        }
+        finally
+        {
+            if (!error)
+            {
+                if (xhrObjects.indexOf(" " + name + " ") != -1)
+                    return new XMLHttpRequestWrapper(activeXObject);
+                else
+                    return activeXObject;
+            }
+            else
+                throw error.message;
+        }
+    };
+}
+
+// ************************************************************************************************
+
+// Register the XMLHttpRequestWrapper for non-IE6 browsers
+if (!isIE6)
+{
+    var _XMLHttpRequest = XMLHttpRequest;
+    window.XMLHttpRequest = function()
+    {
+        return new XMLHttpRequestWrapper();
+    };
+}
+
+//************************************************************************************************
+
+FBL.getNativeXHRObject = function()
+{
+    var xhrObj = false;
+    try
+    {
+        xhrObj = new _XMLHttpRequest();
+    }
+    catch(e)
+    {
+        var progid = [
+                "MSXML2.XMLHTTP.5.0", "MSXML2.XMLHTTP.4.0",
+                "MSXML2.XMLHTTP.3.0", "MSXML2.XMLHTTP", "Microsoft.XMLHTTP"
+            ];
+
+        for ( var i=0; i < progid.length; ++i ) {
+            try
+            {
+                xhrObj = new _ActiveXObject(progid[i]);
+            }
+            catch(e)
+            {
+                continue;
+            }
+            break;
+        }
+    }
+    finally
+    {
+        return xhrObj;
+    }
+};
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var reIgnore = /about:|javascript:|resource:|chrome:|jar:/;
+var layoutInterval = 300;
+var indentWidth = 18;
+
+var cacheSession = null;
+var contexts = new Array();
+var panelName = "net";
+var maxQueueRequests = 500;
+//var panelBar1 = $("fbPanelBar1"); // chrome not available at startup
+var activeRequests = [];
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var mimeExtensionMap =
+{
+    "txt": "text/plain",
+    "html": "text/html",
+    "htm": "text/html",
+    "xhtml": "text/html",
+    "xml": "text/xml",
+    "css": "text/css",
+    "js": "application/x-javascript",
+    "jss": "application/x-javascript",
+    "jpg": "image/jpg",
+    "jpeg": "image/jpeg",
+    "gif": "image/gif",
+    "png": "image/png",
+    "bmp": "image/bmp",
+    "swf": "application/x-shockwave-flash",
+    "flv": "video/x-flv"
+};
+
+var fileCategories =
+{
+    "undefined": 1,
+    "html": 1,
+    "css": 1,
+    "js": 1,
+    "xhr": 1,
+    "image": 1,
+    "flash": 1,
+    "txt": 1,
+    "bin": 1
+};
+
+var textFileCategories =
+{
+    "txt": 1,
+    "html": 1,
+    "xhr": 1,
+    "css": 1,
+    "js": 1
+};
+
+var binaryFileCategories =
+{
+    "bin": 1,
+    "flash": 1
+};
+
+var mimeCategoryMap =
+{
+    "text/plain": "txt",
+    "application/octet-stream": "bin",
+    "text/html": "html",
+    "text/xml": "html",
+    "text/css": "css",
+    "application/x-javascript": "js",
+    "text/javascript": "js",
+    "application/javascript" : "js",
+    "image/jpeg": "image",
+    "image/jpg": "image",
+    "image/gif": "image",
+    "image/png": "image",
+    "image/bmp": "image",
+    "application/x-shockwave-flash": "flash",
+    "video/x-flv": "flash"
+};
+
+var binaryCategoryMap =
+{
+    "image": 1,
+    "flash" : 1
+};
+
+// ************************************************************************************************
+
+/**
+ * @module Represents a module object for the Net panel. This object is derived
+ * from <code>Firebug.ActivableModule</code> in order to support activation (enable/disable).
+ * This allows to avoid (performance) expensive features if the functionality is not necessary
+ * for the user.
+ */
+Firebug.NetMonitor = extend(Firebug.ActivableModule,
+{
+    dispatchName: "netMonitor",
+
+    clear: function(context)
+    {
+        // The user pressed a Clear button so, remove content of the panel...
+        var panel = context.getPanel(panelName, true);
+        if (panel)
+            panel.clear();
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Module
+
+    initialize: function()
+    {
+        return;
+
+        this.panelName = panelName;
+
+        Firebug.ActivableModule.initialize.apply(this, arguments);
+
+        if (Firebug.TraceModule)
+            Firebug.TraceModule.addListener(this.TraceListener);
+
+        // HTTP observer must be registered now (and not in monitorContext, since if a
+        // page is opened in a new tab the top document request would be missed otherwise.
+        NetHttpObserver.registerObserver();
+        NetHttpActivityObserver.registerObserver();
+
+        Firebug.Debugger.addListener(this.DebuggerListener);
+    },
+
+    shutdown: function()
+    {
+        return;
+
+        prefs.removeObserver(Firebug.prefDomain, this, false);
+        if (Firebug.TraceModule)
+            Firebug.TraceModule.removeListener(this.TraceListener);
+
+        NetHttpObserver.unregisterObserver();
+        NetHttpActivityObserver.unregisterObserver();
+
+        Firebug.Debugger.removeListener(this.DebuggerListener);
+    }
+});
+
+
+/**
+ * @domplate Represents a template that is used to reneder detailed info about a request.
+ * This template is rendered when a request is expanded.
+ */
+Firebug.NetMonitor.NetInfoBody = domplate(Firebug.Rep, new Firebug.Listener(),
+{
+    tag:
+        DIV({"class": "netInfoBody", _repObject: "$file"},
+            TAG("$infoTabs", {file: "$file"}),
+            TAG("$infoBodies", {file: "$file"})
+        ),
+
+    infoTabs:
+        DIV({"class": "netInfoTabs focusRow subFocusRow", "role": "tablist"},
+            A({"class": "netInfoParamsTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+                view: "Params",
+                $collapsed: "$file|hideParams"},
+                $STR("URLParameters")
+            ),
+            A({"class": "netInfoHeadersTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+                view: "Headers"},
+                $STR("Headers")
+            ),
+            A({"class": "netInfoPostTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+                view: "Post",
+                $collapsed: "$file|hidePost"},
+                $STR("Post")
+            ),
+            A({"class": "netInfoPutTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+                view: "Put",
+                $collapsed: "$file|hidePut"},
+                $STR("Put")
+            ),
+            A({"class": "netInfoResponseTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+                view: "Response",
+                $collapsed: "$file|hideResponse"},
+                $STR("Response")
+            ),
+            A({"class": "netInfoCacheTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+               view: "Cache",
+               $collapsed: "$file|hideCache"},
+               $STR("Cache")
+            ),
+            A({"class": "netInfoHtmlTab netInfoTab a11yFocus", onclick: "$onClickTab", "role": "tab",
+               view: "Html",
+               $collapsed: "$file|hideHtml"},
+               $STR("HTML")
+            )
+        ),
+
+    infoBodies:
+        DIV({"class": "netInfoBodies outerFocusRow"},
+            TABLE({"class": "netInfoParamsText netInfoText netInfoParamsTable", "role": "tabpanel",
+                    cellpadding: 0, cellspacing: 0}, TBODY()),
+            DIV({"class": "netInfoHeadersText netInfoText", "role": "tabpanel"}),
+            DIV({"class": "netInfoPostText netInfoText", "role": "tabpanel"}),
+            DIV({"class": "netInfoPutText netInfoText", "role": "tabpanel"}),
+            PRE({"class": "netInfoResponseText netInfoText", "role": "tabpanel"}),
+            DIV({"class": "netInfoCacheText netInfoText", "role": "tabpanel"},
+                TABLE({"class": "netInfoCacheTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
+                    TBODY({"role": "list", "aria-label": $STR("Cache")})
+                )
+            ),
+            DIV({"class": "netInfoHtmlText netInfoText", "role": "tabpanel"},
+                IFRAME({"class": "netInfoHtmlPreview", "role": "document"})
+            )
+        ),
+
+    headerDataTag:
+        FOR("param", "$headers",
+            TR({"role": "listitem"},
+                TD({"class": "netInfoParamName", "role": "presentation"},
+                    TAG("$param|getNameTag", {param: "$param"})
+                ),
+                TD({"class": "netInfoParamValue", "role": "list", "aria-label": "$param.name"},
+                    FOR("line", "$param|getParamValueIterator",
+                        CODE({"class": "focusRow subFocusRow", "role": "listitem"}, "$line")
+                    )
+                )
+            )
+        ),
+
+    customTab:
+        A({"class": "netInfo$tabId\\Tab netInfoTab", onclick: "$onClickTab", view: "$tabId", "role": "tab"},
+            "$tabTitle"
+        ),
+
+    customBody:
+        DIV({"class": "netInfo$tabId\\Text netInfoText", "role": "tabpanel"}),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    nameTag:
+        SPAN("$param|getParamName"),
+
+    nameWithTooltipTag:
+        SPAN({title: "$param.name"}, "$param|getParamName"),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getNameTag: function(param)
+    {
+        return (this.getParamName(param) == param.name) ? this.nameTag : this.nameWithTooltipTag;
+    },
+
+    getParamName: function(param)
+    {
+        var limit = 25;
+        var name = param.name;
+        if (name.length > limit)
+            name = name.substr(0, limit) + "...";
+        return name;
+    },
+
+    getParamTitle: function(param)
+    {
+        var limit = 25;
+        var name = param.name;
+        if (name.length > limit)
+            return name;
+        return "";
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    hideParams: function(file)
+    {
+        return !file.urlParams || !file.urlParams.length;
+    },
+
+    hidePost: function(file)
+    {
+        return file.method.toUpperCase() != "POST";
+    },
+
+    hidePut: function(file)
+    {
+        return file.method.toUpperCase() != "PUT";
+    },
+
+    hideResponse: function(file)
+    {
+        return false;
+        //return file.category in binaryFileCategories;
+    },
+
+    hideCache: function(file)
+    {
+        return true;
+        //xxxHonza: I don't see any reason why not to display the cache also info for images.
+        return !file.cacheEntry; // || file.category=="image";
+    },
+
+    hideHtml: function(file)
+    {
+        return (file.mimeType != "text/html") && (file.mimeType != "application/xhtml+xml");
+    },
+
+    onClickTab: function(event)
+    {
+        this.selectTab(event.currentTarget || event.srcElement);
+    },
+
+    getParamValueIterator: function(param)
+    {
+        // TODO: xxxpedro console2
+        return param.value;
+
+        // This value is inserted into CODE element and so, make sure the HTML isn't escaped (1210).
+        // This is why the second parameter is true.
+        // The CODE (with style white-space:pre) element preserves whitespaces so they are
+        // displayed the same, as they come from the server (1194).
+        // In case of a long header values of post parameters the value must be wrapped (2105).
+        return wrapText(param.value, true);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    appendTab: function(netInfoBox, tabId, tabTitle)
+    {
+        // Create new tab and body.
+        var args = {tabId: tabId, tabTitle: tabTitle};
+        ///this.customTab.append(args, netInfoBox.getElementsByClassName("netInfoTabs").item(0));
+        ///this.customBody.append(args, netInfoBox.getElementsByClassName("netInfoBodies").item(0));
+        this.customTab.append(args, $$(".netInfoTabs", netInfoBox)[0]);
+        this.customBody.append(args, $$(".netInfoBodies", netInfoBox)[0]);
+    },
+
+    selectTabByName: function(netInfoBox, tabName)
+    {
+        var tab = getChildByClass(netInfoBox, "netInfoTabs", "netInfo"+tabName+"Tab");
+        if (tab)
+            this.selectTab(tab);
+    },
+
+    selectTab: function(tab)
+    {
+        var view = tab.getAttribute("view");
+
+        var netInfoBox = getAncestorByClass(tab, "netInfoBody");
+
+        var selectedTab = netInfoBox.selectedTab;
+
+        if (selectedTab)
+        {
+            //netInfoBox.selectedText.removeAttribute("selected");
+            removeClass(netInfoBox.selectedText, "netInfoTextSelected");
+
+            removeClass(selectedTab, "netInfoTabSelected");
+            //selectedTab.removeAttribute("selected");
+            selectedTab.setAttribute("aria-selected", "false");
+        }
+
+        var textBodyName = "netInfo" + view + "Text";
+
+        selectedTab = netInfoBox.selectedTab = tab;
+
+        netInfoBox.selectedText = $$("."+textBodyName, netInfoBox)[0];
+        //netInfoBox.selectedText = netInfoBox.getElementsByClassName(textBodyName).item(0);
+
+        //netInfoBox.selectedText.setAttribute("selected", "true");
+        setClass(netInfoBox.selectedText, "netInfoTextSelected");
+
+        setClass(selectedTab, "netInfoTabSelected");
+        selectedTab.setAttribute("selected", "true");
+        selectedTab.setAttribute("aria-selected", "true");
+
+        var file = Firebug.getRepObject(netInfoBox);
+
+        //var context = Firebug.getElementPanel(netInfoBox).context;
+        var context = Firebug.chrome;
+
+        this.updateInfo(netInfoBox, file, context);
+    },
+
+    updateInfo: function(netInfoBox, file, context)
+    {
+        if (FBTrace.DBG_NET)
+            FBTrace.sysout("net.updateInfo; file", file);
+
+        if (!netInfoBox)
+        {
+            if (FBTrace.DBG_NET || FBTrace.DBG_ERRORS)
+                FBTrace.sysout("net.updateInfo; ERROR netInfo == null " + file.href, file);
+            return;
+        }
+
+        var tab = netInfoBox.selectedTab;
+
+        if (hasClass(tab, "netInfoParamsTab"))
+        {
+            if (file.urlParams && !netInfoBox.urlParamsPresented)
+            {
+                netInfoBox.urlParamsPresented = true;
+                this.insertHeaderRows(netInfoBox, file.urlParams, "Params");
+            }
+        }
+
+        else if (hasClass(tab, "netInfoHeadersTab"))
+        {
+            var headersText = $$(".netInfoHeadersText", netInfoBox)[0];
+            //var headersText = netInfoBox.getElementsByClassName("netInfoHeadersText").item(0);
+
+            if (file.responseHeaders && !netInfoBox.responseHeadersPresented)
+            {
+                netInfoBox.responseHeadersPresented = true;
+                NetInfoHeaders.renderHeaders(headersText, file.responseHeaders, "ResponseHeaders");
+            }
+
+            if (file.requestHeaders && !netInfoBox.requestHeadersPresented)
+            {
+                netInfoBox.requestHeadersPresented = true;
+                NetInfoHeaders.renderHeaders(headersText, file.requestHeaders, "RequestHeaders");
+            }
+        }
+
+        else if (hasClass(tab, "netInfoPostTab"))
+        {
+            if (!netInfoBox.postPresented)
+            {
+                netInfoBox.postPresented  = true;
+                //var postText = netInfoBox.getElementsByClassName("netInfoPostText").item(0);
+                var postText = $$(".netInfoPostText", netInfoBox)[0];
+                NetInfoPostData.render(context, postText, file);
+            }
+        }
+
+        else if (hasClass(tab, "netInfoPutTab"))
+        {
+            if (!netInfoBox.putPresented)
+            {
+                netInfoBox.putPresented  = true;
+                //var putText = netInfoBox.getElementsByClassName("netInfoPutText").item(0);
+                var putText = $$(".netInfoPutText", netInfoBox)[0];
+                NetInfoPostData.render(context, putText, file);
+            }
+        }
+
+        else if (hasClass(tab, "netInfoResponseTab") && file.loaded && !netInfoBox.responsePresented)
+        {
+            ///var responseTextBox = netInfoBox.getElementsByClassName("netInfoResponseText").item(0);
+            var responseTextBox = $$(".netInfoResponseText", netInfoBox)[0];
+            if (file.category == "image")
+            {
+                netInfoBox.responsePresented = true;
+
+                var responseImage = netInfoBox.ownerDocument.createElement("img");
+                responseImage.src = file.href;
+
+                clearNode(responseTextBox);
+                responseTextBox.appendChild(responseImage, responseTextBox);
+            }
+            else ///if (!(binaryCategoryMap.hasOwnProperty(file.category)))
+            {
+                this.setResponseText(file, netInfoBox, responseTextBox, context);
+            }
+        }
+
+        else if (hasClass(tab, "netInfoCacheTab") && file.loaded && !netInfoBox.cachePresented)
+        {
+            var responseTextBox = netInfoBox.getElementsByClassName("netInfoCacheText").item(0);
+            if (file.cacheEntry) {
+                netInfoBox.cachePresented = true;
+                this.insertHeaderRows(netInfoBox, file.cacheEntry, "Cache");
+            }
+        }
+
+        else if (hasClass(tab, "netInfoHtmlTab") && file.loaded && !netInfoBox.htmlPresented)
+        {
+            netInfoBox.htmlPresented = true;
+
+            var text = Utils.getResponseText(file, context);
+
+            ///var iframe = netInfoBox.getElementsByClassName("netInfoHtmlPreview").item(0);
+            var iframe = $$(".netInfoHtmlPreview", netInfoBox)[0];
+
+            ///iframe.contentWindow.document.body.innerHTML = text;
+
+            // TODO: xxxpedro net - remove scripts
+            var reScript = /<script(.|\s)*?\/script>/gi;
+
+            text = text.replace(reScript, "");
+
+            iframe.contentWindow.document.write(text);
+            iframe.contentWindow.document.close();
+        }
+
+        // Notify listeners about update so, content of custom tabs can be updated.
+        dispatch(NetInfoBody.fbListeners, "updateTabBody", [netInfoBox, file, context]);
+    },
+
+    setResponseText: function(file, netInfoBox, responseTextBox, context)
+    {
+        //**********************************************
+        //**********************************************
+        //**********************************************
+        netInfoBox.responsePresented = true;
+        // line breaks somehow are different in IE
+        // make this only once in the initialization? we don't have net panels and modules yet.
+        if (isIE)
+            responseTextBox.style.whiteSpace = "nowrap";
+
+        responseTextBox[
+                typeof responseTextBox.textContent != "undefined" ?
+                        "textContent" :
+                        "innerText"
+            ] = file.responseText;
+
+        return;
+        //**********************************************
+        //**********************************************
+        //**********************************************
+
+        // Get response text and make sure it doesn't exceed the max limit.
+        var text = Utils.getResponseText(file, context);
+        var limit = Firebug.netDisplayedResponseLimit + 15;
+        var limitReached = text ? (text.length > limit) : false;
+        if (limitReached)
+            text = text.substr(0, limit) + "...";
+
+        // Insert the response into the UI.
+        if (text)
+            insertWrappedText(text, responseTextBox);
+        else
+            insertWrappedText("", responseTextBox);
+
+        // Append a message informing the user that the response isn't fully displayed.
+        if (limitReached)
+        {
+            var object = {
+                text: $STR("net.responseSizeLimitMessage"),
+                onClickLink: function() {
+                    var panel = context.getPanel("net", true);
+                    panel.openResponseInTab(file);
+                }
+            };
+            Firebug.NetMonitor.ResponseSizeLimit.append(object, responseTextBox);
+        }
+
+        netInfoBox.responsePresented = true;
+
+        if (FBTrace.DBG_NET)
+            FBTrace.sysout("net.setResponseText; response text updated");
+    },
+
+    insertHeaderRows: function(netInfoBox, headers, tableName, rowName)
+    {
+        if (!headers.length)
+            return;
+
+        var headersTable = $$(".netInfo"+tableName+"Table", netInfoBox)[0];
+        //var headersTable = netInfoBox.getElementsByClassName("netInfo"+tableName+"Table").item(0);
+        var tbody = getChildByClass(headersTable, "netInfo" + rowName + "Body");
+        if (!tbody)
+            tbody = headersTable.firstChild;
+        var titleRow = getChildByClass(tbody, "netInfo" + rowName + "Title");
+
+        this.headerDataTag.insertRows({headers: headers}, titleRow ? titleRow : tbody);
+        removeClass(titleRow, "collapsed");
+    }
+});
+
+var NetInfoBody = Firebug.NetMonitor.NetInfoBody;
+
+// ************************************************************************************************
+
+/**
+ * @domplate Used within the Net panel to display raw source of request and response headers
+ * as well as pretty-formatted summary of these headers.
+ */
+Firebug.NetMonitor.NetInfoHeaders = domplate(Firebug.Rep, //new Firebug.Listener(),
+{
+    tag:
+        DIV({"class": "netInfoHeadersTable", "role": "tabpanel"},
+            DIV({"class": "netInfoHeadersGroup netInfoResponseHeadersTitle"},
+                SPAN($STR("ResponseHeaders")),
+                SPAN({"class": "netHeadersViewSource response collapsed", onclick: "$onViewSource",
+                    _sourceDisplayed: false, _rowName: "ResponseHeaders"},
+                    $STR("net.headers.view source")
+                )
+            ),
+            TABLE({cellpadding: 0, cellspacing: 0},
+                TBODY({"class": "netInfoResponseHeadersBody", "role": "list",
+                    "aria-label": $STR("ResponseHeaders")})
+            ),
+            DIV({"class": "netInfoHeadersGroup netInfoRequestHeadersTitle"},
+                SPAN($STR("RequestHeaders")),
+                SPAN({"class": "netHeadersViewSource request collapsed", onclick: "$onViewSource",
+                    _sourceDisplayed: false, _rowName: "RequestHeaders"},
+                    $STR("net.headers.view source")
+                )
+            ),
+            TABLE({cellpadding: 0, cellspacing: 0},
+                TBODY({"class": "netInfoRequestHeadersBody", "role": "list",
+                    "aria-label": $STR("RequestHeaders")})
+            )
+        ),
+
+    sourceTag:
+        TR({"role": "presentation"},
+            TD({colspan: 2, "role": "presentation"},
+                PRE({"class": "source"})
+            )
+        ),
+
+    onViewSource: function(event)
+    {
+        var target = event.target;
+        var requestHeaders = (target.rowName == "RequestHeaders");
+
+        var netInfoBox = getAncestorByClass(target, "netInfoBody");
+        var file = netInfoBox.repObject;
+
+        if (target.sourceDisplayed)
+        {
+            var headers = requestHeaders ? file.requestHeaders : file.responseHeaders;
+            this.insertHeaderRows(netInfoBox, headers, target.rowName);
+            target.innerHTML = $STR("net.headers.view source");
+        }
+        else
+        {
+            var source = requestHeaders ? file.requestHeadersText : file.responseHeadersText;
+            this.insertSource(netInfoBox, source, target.rowName);
+            target.innerHTML = $STR("net.headers.pretty print");
+        }
+
+        target.sourceDisplayed = !target.sourceDisplayed;
+
+        cancelEvent(event);
+    },
+
+    insertSource: function(netInfoBox, source, rowName)
+    {
+        // This breaks copy to clipboard.
+        //if (source)
+        //    source = source.replace(/\r\n/gm, "<span style='color:lightgray'>\\r\\n</span>\r\n");
+
+        ///var tbody = netInfoBox.getElementsByClassName("netInfo" + rowName + "Body").item(0);
+        var tbody = $$(".netInfo" + rowName + "Body", netInfoBox)[0];
+        var node = this.sourceTag.replace({}, tbody);
+        ///var sourceNode = node.getElementsByClassName("source").item(0);
+        var sourceNode = $$(".source", node)[0];
+        sourceNode.innerHTML = source;
+    },
+
+    insertHeaderRows: function(netInfoBox, headers, rowName)
+    {
+        var headersTable = $$(".netInfoHeadersTable", netInfoBox)[0];
+        var tbody = $$(".netInfo" + rowName + "Body", headersTable)[0];
+
+        //var headersTable = netInfoBox.getElementsByClassName("netInfoHeadersTable").item(0);
+        //var tbody = headersTable.getElementsByClassName("netInfo" + rowName + "Body").item(0);
+
+        clearNode(tbody);
+
+        if (!headers.length)
+            return;
+
+        NetInfoBody.headerDataTag.insertRows({headers: headers}, tbody);
+
+        var titleRow = getChildByClass(headersTable, "netInfo" + rowName + "Title");
+        removeClass(titleRow, "collapsed");
+    },
+
+    init: function(parent)
+    {
+        var rootNode = this.tag.append({}, parent);
+
+        var netInfoBox = getAncestorByClass(parent, "netInfoBody");
+        var file = netInfoBox.repObject;
+
+        var viewSource;
+
+        viewSource = $$(".request", rootNode)[0];
+        //viewSource = rootNode.getElementsByClassName("netHeadersViewSource request").item(0);
+        if (file.requestHeadersText)
+            removeClass(viewSource, "collapsed");
+
+        viewSource = $$(".response", rootNode)[0];
+        //viewSource = rootNode.getElementsByClassName("netHeadersViewSource response").item(0);
+        if (file.responseHeadersText)
+            removeClass(viewSource, "collapsed");
+    },
+
+    renderHeaders: function(parent, headers, rowName)
+    {
+        if (!parent.firstChild)
+            this.init(parent);
+
+        this.insertHeaderRows(parent, headers, rowName);
+    }
+});
+
+var NetInfoHeaders = Firebug.NetMonitor.NetInfoHeaders;
+
+// ************************************************************************************************
+
+/**
+ * @domplate Represents posted data within request info (the info, which is visible when
+ * a request entry is expanded. This template renders content of the Post tab.
+ */
+Firebug.NetMonitor.NetInfoPostData = domplate(Firebug.Rep, /*new Firebug.Listener(),*/
+{
+    // application/x-www-form-urlencoded
+    paramsTable:
+        TABLE({"class": "netInfoPostParamsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
+            TBODY({"role": "list", "aria-label": $STR("net.label.Parameters")},
+                TR({"class": "netInfoPostParamsTitle", "role": "presentation"},
+                    TD({colspan: 3, "role": "presentation"},
+                        DIV({"class": "netInfoPostParams"},
+                            $STR("net.label.Parameters"),
+                            SPAN({"class": "netInfoPostContentType"},
+                                "application/x-www-form-urlencoded"
+                            )
+                        )
+                    )
+                )
+            )
+        ),
+
+    // multipart/form-data
+    partsTable:
+        TABLE({"class": "netInfoPostPartsTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
+            TBODY({"role": "list", "aria-label": $STR("net.label.Parts")},
+                TR({"class": "netInfoPostPartsTitle", "role": "presentation"},
+                    TD({colspan: 2, "role":"presentation" },
+                        DIV({"class": "netInfoPostParams"},
+                            $STR("net.label.Parts"),
+                            SPAN({"class": "netInfoPostContentType"},
+                                "multipart/form-data"
+                            )
+                        )
+                    )
+                )
+            )
+        ),
+
+    // application/json
+    jsonTable:
+        TABLE({"class": "netInfoPostJSONTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
+            ///TBODY({"role": "list", "aria-label": $STR("jsonviewer.tab.JSON")},
+            TBODY({"role": "list", "aria-label": $STR("JSON")},
+                TR({"class": "netInfoPostJSONTitle", "role": "presentation"},
+                    TD({"role": "presentation" },
+                        DIV({"class": "netInfoPostParams"},
+                            ///$STR("jsonviewer.tab.JSON")
+                            $STR("JSON")
+                        )
+                    )
+                ),
+                TR(
+                    TD({"class": "netInfoPostJSONBody"})
+                )
+            )
+        ),
+
+    // application/xml
+    xmlTable:
+        TABLE({"class": "netInfoPostXMLTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
+            TBODY({"role": "list", "aria-label": $STR("xmlviewer.tab.XML")},
+                TR({"class": "netInfoPostXMLTitle", "role": "presentation"},
+                    TD({"role": "presentation" },
+                        DIV({"class": "netInfoPostParams"},
+                            $STR("xmlviewer.tab.XML")
+                        )
+                    )
+                ),
+                TR(
+                    TD({"class": "netInfoPostXMLBody"})
+                )
+            )
+        ),
+
+    sourceTable:
+        TABLE({"class": "netInfoPostSourceTable", cellpadding: 0, cellspacing: 0, "role": "presentation"},
+            TBODY({"role": "list", "aria-label": $STR("net.label.Source")},
+                TR({"class": "netInfoPostSourceTitle", "role": "presentation"},
+                    TD({colspan: 2, "role": "presentation"},
+                        DIV({"class": "netInfoPostSource"},
+                            $STR("net.label.Source")
+                        )
+                    )
+                )
+            )
+        ),
+
+    sourceBodyTag:
+        TR({"role": "presentation"},
+            TD({colspan: 2, "role": "presentation"},
+                FOR("line", "$param|getParamValueIterator",
+                    CODE({"class":"focusRow subFocusRow" , "role": "listitem"},"$line")
+                )
+            )
+        ),
+
+    getParamValueIterator: function(param)
+    {
+        return NetInfoBody.getParamValueIterator(param);
+    },
+
+    render: function(context, parentNode, file)
+    {
+        //debugger;
+        var spy = getAncestorByClass(parentNode, "spyHead");
+        var spyObject = spy.repObject;
+        var data = spyObject.data;
+
+        ///var contentType = Utils.findHeader(file.requestHeaders, "content-type");
+        var contentType = file.mimeType;
+
+        ///var text = Utils.getPostText(file, context, true);
+        ///if (text == undefined)
+        ///    return;
+
+        ///if (Utils.isURLEncodedRequest(file, context))
+        // fake Utils.isURLEncodedRequest identification
+        if (contentType && contentType == "application/x-www-form-urlencoded" ||
+            data && data.indexOf("=") != -1)
+        {
+            ///var lines = text.split("\n");
+            ///var params = parseURLEncodedText(lines[lines.length-1]);
+            var params = parseURLEncodedTextArray(data);
+            if (params)
+                this.insertParameters(parentNode, params);
+        }
+
+        ///if (Utils.isMultiPartRequest(file, context))
+        ///{
+        ///    var data = this.parseMultiPartText(file, context);
+        ///    if (data)
+        ///        this.insertParts(parentNode, data);
+        ///}
+
+        // moved to the top
+        ///var contentType = Utils.findHeader(file.requestHeaders, "content-type");
+
+        ///if (Firebug.JSONViewerModel.isJSON(contentType))
+        var jsonData = {
+            responseText: data
+        };
+
+        if (Firebug.JSONViewerModel.isJSON(contentType, data))
+            ///this.insertJSON(parentNode, file, context);
+            this.insertJSON(parentNode, jsonData, context);
+
+        ///if (Firebug.XMLViewerModel.isXML(contentType))
+        ///    this.insertXML(parentNode, file, context);
+
+        ///var postText = Utils.getPostText(file, context);
+        ///postText = Utils.formatPostText(postText);
+        var postText = data;
+        if (postText)
+            this.insertSource(parentNode, postText);
+    },
+
+    insertParameters: function(parentNode, params)
+    {
+        if (!params || !params.length)
+            return;
+
+        var paramTable = this.paramsTable.append({object:{}}, parentNode);
+        var row = $$(".netInfoPostParamsTitle", paramTable)[0];
+        //var paramTable = this.paramsTable.append(null, parentNode);
+        //var row = paramTable.getElementsByClassName("netInfoPostParamsTitle").item(0);
+
+        var tbody = paramTable.getElementsByTagName("tbody")[0];
+
+        NetInfoBody.headerDataTag.insertRows({headers: params}, row);
+    },
+
+    insertParts: function(parentNode, data)
+    {
+        if (!data.params || !data.params.length)
+            return;
+
+        var partsTable = this.partsTable.append({object:{}}, parentNode);
+        var row = $$(".netInfoPostPartsTitle", paramTable)[0];
+        //var partsTable = this.partsTable.append(null, parentNode);
+        //var row = partsTable.getElementsByClassName("netInfoPostPartsTitle").item(0);
+
+        NetInfoBody.headerDataTag.insertRows({headers: data.params}, row);
+    },
+
+    insertJSON: function(parentNode, file, context)
+    {
+        ///var text = Utils.getPostText(file, context);
+        var text = file.responseText;
+        ///var data = parseJSONString(text, "http://" + file.request.originalURI.host);
+        var data = parseJSONString(text);
+        if (!data)
+            return;
+
+        ///var jsonTable = this.jsonTable.append(null, parentNode);
+        var jsonTable = this.jsonTable.append({}, parentNode);
+        ///var jsonBody = jsonTable.getElementsByClassName("netInfoPostJSONBody").item(0);
+        var jsonBody = $$(".netInfoPostJSONBody", jsonTable)[0];
+
+        if (!this.toggles)
+            this.toggles = {};
+
+        Firebug.DOMPanel.DirTable.tag.replace(
+            {object: data, toggles: this.toggles}, jsonBody);
+    },
+
+    insertXML: function(parentNode, file, context)
+    {
+        var text = Utils.getPostText(file, context);
+
+        var jsonTable = this.xmlTable.append(null, parentNode);
+        ///var jsonBody = jsonTable.getElementsByClassName("netInfoPostXMLBody").item(0);
+        var jsonBody = $$(".netInfoPostXMLBody", jsonTable)[0];
+
+        Firebug.XMLViewerModel.insertXML(jsonBody, text);
+    },
+
+    insertSource: function(parentNode, text)
+    {
+        var sourceTable = this.sourceTable.append({object:{}}, parentNode);
+        var row = $$(".netInfoPostSourceTitle", sourceTable)[0];
+        //var sourceTable = this.sourceTable.append(null, parentNode);
+        //var row = sourceTable.getElementsByClassName("netInfoPostSourceTitle").item(0);
+
+        var param = {value: [text]};
+        this.sourceBodyTag.insertRows({param: param}, row);
+    },
+
+    parseMultiPartText: function(file, context)
+    {
+        var text = Utils.getPostText(file, context);
+        if (text == undefined)
+            return null;
+
+        FBTrace.sysout("net.parseMultiPartText; boundary: ", text);
+
+        var boundary = text.match(/\s*boundary=\s*(.*)/)[1];
+
+        var divider = "\r\n\r\n";
+        var bodyStart = text.indexOf(divider);
+        var body = text.substr(bodyStart + divider.length);
+
+        var postData = {};
+        postData.mimeType = "multipart/form-data";
+        postData.params = [];
+
+        var parts = body.split("--" + boundary);
+        for (var i=0; i<parts.length; i++)
+        {
+            var part = parts[i].split(divider);
+            if (part.length != 2)
+                continue;
+
+            var m = part[0].match(/\s*name=\"(.*)\"(;|$)/);
+            postData.params.push({
+                name: (m && m.length > 1) ? m[1] : "",
+                value: trim(part[1])
+            });
+        }
+
+        return postData;
+    }
+});
+
+var NetInfoPostData = Firebug.NetMonitor.NetInfoPostData;
+
+// ************************************************************************************************
+
+
+// TODO: xxxpedro net i18n
+var $STRP = function(a){return a;};
+
+Firebug.NetMonitor.NetLimit = domplate(Firebug.Rep,
+{
+    collapsed: true,
+
+    tableTag:
+        DIV(
+            TABLE({width: "100%", cellpadding: 0, cellspacing: 0},
+                TBODY()
+            )
+        ),
+
+    limitTag:
+        TR({"class": "netRow netLimitRow", $collapsed: "$isCollapsed"},
+            TD({"class": "netCol netLimitCol", colspan: 6},
+                TABLE({cellpadding: 0, cellspacing: 0},
+                    TBODY(
+                        TR(
+                            TD(
+                                SPAN({"class": "netLimitLabel"},
+                                    $STRP("plural.Limit_Exceeded", [0])
+                                )
+                            ),
+                            TD({style: "width:100%"}),
+                            TD(
+                                BUTTON({"class": "netLimitButton", title: "$limitPrefsTitle",
+                                    onclick: "$onPreferences"},
+                                  $STR("LimitPrefs")
+                                )
+                            ),
+                            TD("&nbsp;")
+                        )
+                    )
+                )
+            )
+        ),
+
+    isCollapsed: function()
+    {
+        return this.collapsed;
+    },
+
+    onPreferences: function(event)
+    {
+        openNewTab("about:config");
+    },
+
+    updateCounter: function(row)
+    {
+        removeClass(row, "collapsed");
+
+        // Update info within the limit row.
+        var limitLabel = row.getElementsByClassName("netLimitLabel").item(0);
+        limitLabel.firstChild.nodeValue = $STRP("plural.Limit_Exceeded", [row.limitInfo.totalCount]);
+    },
+
+    createTable: function(parent, limitInfo)
+    {
+        var table = this.tableTag.replace({}, parent);
+        var row = this.createRow(table.firstChild.firstChild, limitInfo);
+        return [table, row];
+    },
+
+    createRow: function(parent, limitInfo)
+    {
+        var row = this.limitTag.insertRows(limitInfo, parent, this)[0];
+        row.limitInfo = limitInfo;
+        return row;
+    },
+
+    // nsIPrefObserver
+    observe: function(subject, topic, data)
+    {
+        // We're observing preferences only.
+        if (topic != "nsPref:changed")
+          return;
+
+        if (data.indexOf("net.logLimit") != -1)
+            this.updateMaxLimit();
+    },
+
+    updateMaxLimit: function()
+    {
+        var value = Firebug.getPref(Firebug.prefDomain, "net.logLimit");
+        maxQueueRequests = value ? value : maxQueueRequests;
+    }
+});
+
+var NetLimit = Firebug.NetMonitor.NetLimit;
+
+// ************************************************************************************************
+
+Firebug.NetMonitor.ResponseSizeLimit = domplate(Firebug.Rep,
+{
+    tag:
+        DIV({"class": "netInfoResponseSizeLimit"},
+            SPAN("$object.beforeLink"),
+            A({"class": "objectLink", onclick: "$onClickLink"},
+                "$object.linkText"
+            ),
+            SPAN("$object.afterLink")
+        ),
+
+    reLink: /^(.*)<a>(.*)<\/a>(.*$)/,
+    append: function(obj, parent)
+    {
+        var m = obj.text.match(this.reLink);
+        return this.tag.append({onClickLink: obj.onClickLink,
+            object: {
+            beforeLink: m[1],
+            linkText: m[2],
+            afterLink: m[3]
+        }}, parent, this);
+    }
+});
+
+// ************************************************************************************************
+// ************************************************************************************************
+
+Firebug.NetMonitor.Utils =
+{
+    findHeader: function(headers, name)
+    {
+        if (!headers)
+            return null;
+
+        name = name.toLowerCase();
+        for (var i = 0; i < headers.length; ++i)
+        {
+            var headerName = headers[i].name.toLowerCase();
+            if (headerName == name)
+                return headers[i].value;
+        }
+    },
+
+    formatPostText: function(text)
+    {
+        if (text instanceof XMLDocument)
+            return getElementXML(text.documentElement);
+        else
+            return text;
+    },
+
+    getPostText: function(file, context, noLimit)
+    {
+        if (!file.postText)
+        {
+            file.postText = readPostTextFromRequest(file.request, context);
+
+            if (!file.postText && context)
+                file.postText = readPostTextFromPage(file.href, context);
+        }
+
+        if (!file.postText)
+            return file.postText;
+
+        var limit = Firebug.netDisplayedPostBodyLimit;
+        if (file.postText.length > limit && !noLimit)
+        {
+            return cropString(file.postText, limit,
+                "\n\n... " + $STR("net.postDataSizeLimitMessage") + " ...\n\n");
+        }
+
+        return file.postText;
+    },
+
+    getResponseText: function(file, context)
+    {
+        // The response can be also empty string so, check agains "undefined".
+        return (typeof(file.responseText) != "undefined")? file.responseText :
+            context.sourceCache.loadText(file.href, file.method, file);
+    },
+
+    isURLEncodedRequest: function(file, context)
+    {
+        var text = Utils.getPostText(file, context);
+        if (text && text.toLowerCase().indexOf("content-type: application/x-www-form-urlencoded") == 0)
+            return true;
+
+        // The header value doesn't have to be always exactly "application/x-www-form-urlencoded",
+        // there can be even charset specified. So, use indexOf rather than just "==".
+        var headerValue = Utils.findHeader(file.requestHeaders, "content-type");
+        if (headerValue && headerValue.indexOf("application/x-www-form-urlencoded") == 0)
+            return true;
+
+        return false;
+    },
+
+    isMultiPartRequest: function(file, context)
+    {
+        var text = Utils.getPostText(file, context);
+        if (text && text.toLowerCase().indexOf("content-type: multipart/form-data") == 0)
+            return true;
+        return false;
+    },
+
+    getMimeType: function(mimeType, uri)
+    {
+        if (!mimeType || !(mimeCategoryMap.hasOwnProperty(mimeType)))
+        {
+            var ext = getFileExtension(uri);
+            if (!ext)
+                return mimeType;
+            else
+            {
+                var extMimeType = mimeExtensionMap[ext.toLowerCase()];
+                return extMimeType ? extMimeType : mimeType;
+            }
+        }
+        else
+            return mimeType;
+    },
+
+    getDateFromSeconds: function(s)
+    {
+        var d = new Date();
+        d.setTime(s*1000);
+        return d;
+    },
+
+    getHttpHeaders: function(request, file)
+    {
+        try
+        {
+            var http = QI(request, Ci.nsIHttpChannel);
+            file.status = request.responseStatus;
+
+            // xxxHonza: is there any problem to do this in requestedFile method?
+            file.method = http.requestMethod;
+            file.urlParams = parseURLParams(file.href);
+            file.mimeType = Utils.getMimeType(request.contentType, request.name);
+
+            if (!file.responseHeaders && Firebug.collectHttpHeaders)
+            {
+                var requestHeaders = [], responseHeaders = [];
+
+                http.visitRequestHeaders({
+                    visitHeader: function(name, value)
+                    {
+                        requestHeaders.push({name: name, value: value});
+                    }
+                });
+                http.visitResponseHeaders({
+                    visitHeader: function(name, value)
+                    {
+                        responseHeaders.push({name: name, value: value});
+                    }
+                });
+
+                file.requestHeaders = requestHeaders;
+                file.responseHeaders = responseHeaders;
+            }
+        }
+        catch (exc)
+        {
+            // An exception can be throwed e.g. when the request is aborted and
+            // request.responseStatus is accessed.
+            if (FBTrace.DBG_ERRORS)
+                FBTrace.sysout("net.getHttpHeaders FAILS " + file.href, exc);
+        }
+    },
+
+    isXHR: function(request)
+    {
+        try
+        {
+            var callbacks = request.notificationCallbacks;
+            var xhrRequest = callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null;
+            if (FBTrace.DBG_NET)
+                FBTrace.sysout("net.isXHR; " + (xhrRequest != null) + ", " + safeGetName(request));
+
+            return (xhrRequest != null);
+        }
+        catch (exc)
+        {
+        }
+
+       return false;
+    },
+
+    getFileCategory: function(file)
+    {
+        if (file.category)
+        {
+            if (FBTrace.DBG_NET)
+                FBTrace.sysout("net.getFileCategory; current: " + file.category + " for: " + file.href, file);
+            return file.category;
+        }
+
+        if (file.isXHR)
+        {
+            if (FBTrace.DBG_NET)
+                FBTrace.sysout("net.getFileCategory; XHR for: " + file.href, file);
+            return file.category = "xhr";
+        }
+
+        if (!file.mimeType)
+        {
+            var ext = getFileExtension(file.href);
+            if (ext)
+                file.mimeType = mimeExtensionMap[ext.toLowerCase()];
+        }
+
+        /*if (FBTrace.DBG_NET)
+            FBTrace.sysout("net.getFileCategory; " + mimeCategoryMap[file.mimeType] +
+                ", mimeType: " + file.mimeType + " for: " + file.href, file);*/
+
+        if (!file.mimeType)
+            return "";
+
+        // Solve cases when charset is also specified, eg "text/html; charset=UTF-8".
+        var mimeType = file.mimeType;
+        if (mimeType)
+            mimeType = mimeType.split(";")[0];
+
+        return (file.category = mimeCategoryMap[mimeType]);
+    }
+};
+
+var Utils = Firebug.NetMonitor.Utils;
+
+// ************************************************************************************************
+
+//Firebug.registerRep(Firebug.NetMonitor.NetRequestTable);
+//Firebug.registerActivableModule(Firebug.NetMonitor);
+//Firebug.registerPanel(NetPanel);
+
+Firebug.registerModule(Firebug.NetMonitor);
+//Firebug.registerRep(Firebug.NetMonitor.BreakpointRep);
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// Constants
+
+//const Cc = Components.classes;
+//const Ci = Components.interfaces;
+
+// List of contexts with XHR spy attached.
+var contexts = [];
+
+// ************************************************************************************************
+// Spy Module
+
+/**
+ * @module Represents a XHR Spy module. The main purpose of the XHR Spy feature is to monitor
+ * XHR activity of the current page and create appropriate log into the Console panel.
+ * This feature can be controlled by an option <i>Show XMLHttpRequests</i> (from within the
+ * console panel).
+ *
+ * The module is responsible for attaching/detaching a HTTP Observers when Firebug is
+ * activated/deactivated for a site.
+ */
+Firebug.Spy = extend(Firebug.Module,
+/** @lends Firebug.Spy */
+{
+    dispatchName: "spy",
+
+    initialize: function()
+    {
+        if (Firebug.TraceModule)
+            Firebug.TraceModule.addListener(this.TraceListener);
+
+        Firebug.Module.initialize.apply(this, arguments);
+    },
+
+    shutdown: function()
+    {
+        Firebug.Module.shutdown.apply(this, arguments);
+
+        if (Firebug.TraceModule)
+            Firebug.TraceModule.removeListener(this.TraceListener);
+    },
+
+    initContext: function(context)
+    {
+        context.spies = [];
+
+        if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled())
+            this.attachObserver(context, context.window);
+
+        if (FBTrace.DBG_SPY)
+            FBTrace.sysout("spy.initContext " + contexts.length + " ", context.getName());
+    },
+
+    destroyContext: function(context)
+    {
+        // For any spies that are in progress, remove our listeners so that they don't leak
+        this.detachObserver(context, null);
+
+        if (FBTrace.DBG_SPY && context.spies.length)
+            FBTrace.sysout("spy.destroyContext; ERROR There are leaking Spies ("
+                + context.spies.length + ") " + context.getName());
+
+        delete context.spies;
+
+        if (FBTrace.DBG_SPY)
+            FBTrace.sysout("spy.destroyContext " + contexts.length + " ", context.getName());
+    },
+
+    watchWindow: function(context, win)
+    {
+        if (Firebug.showXMLHttpRequests && Firebug.Console.isAlwaysEnabled())
+            this.attachObserver(context, win);
+    },
+
+    unwatchWindow: function(context, win)
+    {
+        try
+        {
+            // This make sure that the existing context is properly removed from "contexts" array.
+            this.detachObserver(context, win);
+        }
+        catch (ex)
+        {
+            // Get exceptions here sometimes, so let's just ignore them
+            // since the window is going away anyhow
+            ERROR(ex);
+        }
+    },
+
+    updateOption: function(name, value)
+    {
+        // XXXjjb Honza, if Console.isEnabled(context) false, then this can't be called,
+        // but somehow seems not correct
+        if (name == "showXMLHttpRequests")
+        {
+            var tach = value ? this.attachObserver : this.detachObserver;
+            for (var i = 0; i < TabWatcher.contexts.length; ++i)
+            {
+                var context = TabWatcher.contexts[i];
+                iterateWindows(context.window, function(win)
+                {
+                    tach.apply(this, [context, win]);
+                });
+            }
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Attaching Spy to XHR requests.
+
+    /**
+     * Returns false if Spy should not be attached to XHRs executed by the specified window.
+     */
+    skipSpy: function(win)
+    {
+        if (!win)
+            return true;
+
+        // Don't attach spy to chrome.
+        var uri = safeGetWindowLocation(win);
+        if (uri && (uri.indexOf("about:") == 0 || uri.indexOf("chrome:") == 0))
+            return true;
+    },
+
+    attachObserver: function(context, win)
+    {
+        if (Firebug.Spy.skipSpy(win))
+            return;
+
+        for (var i=0; i<contexts.length; ++i)
+        {
+            if ((contexts[i].context == context) && (contexts[i].win == win))
+                return;
+        }
+
+        // Register HTTP observers only once.
+        if (contexts.length == 0)
+        {
+            httpObserver.addObserver(SpyHttpObserver, "firebug-http-event", false);
+            SpyHttpActivityObserver.registerObserver();
+        }
+
+        contexts.push({context: context, win: win});
+
+        if (FBTrace.DBG_SPY)
+            FBTrace.sysout("spy.attachObserver (HTTP) " + contexts.length + " ", context.getName());
+    },
+
+    detachObserver: function(context, win)
+    {
+        for (var i=0; i<contexts.length; ++i)
+        {
+            if (contexts[i].context == context)
+            {
+                if (win && (contexts[i].win != win))
+                    continue;
+
+                contexts.splice(i, 1);
+
+                // If no context is using spy, remvove the (only one) HTTP observer.
+                if (contexts.length == 0)
+                {
+                    httpObserver.removeObserver(SpyHttpObserver, "firebug-http-event");
+                    SpyHttpActivityObserver.unregisterObserver();
+                }
+
+                if (FBTrace.DBG_SPY)
+                    FBTrace.sysout("spy.detachObserver (HTTP) " + contexts.length + " ",
+                        context.getName());
+                return;
+            }
+        }
+    },
+
+    /**
+     * Return XHR object that is associated with specified request <i>nsIHttpChannel</i>.
+     * Returns null if the request doesn't represent XHR.
+     */
+    getXHR: function(request)
+    {
+        // Does also query-interface for nsIHttpChannel.
+        if (!(request instanceof Ci.nsIHttpChannel))
+            return null;
+
+        try
+        {
+            var callbacks = request.notificationCallbacks;
+            return (callbacks ? callbacks.getInterface(Ci.nsIXMLHttpRequest) : null);
+        }
+        catch (exc)
+        {
+            if (exc.name == "NS_NOINTERFACE")
+            {
+                if (FBTrace.DBG_SPY)
+                    FBTrace.sysout("spy.getXHR; Request is not nsIXMLHttpRequest: " +
+                        safeGetRequestName(request));
+            }
+        }
+
+       return null;
+    }
+});
+
+
+
+
+
+// ************************************************************************************************
+
+/*
+function getSpyForXHR(request, xhrRequest, context, noCreate)
+{
+    var spy = null;
+
+    // Iterate all existing spy objects in this context and look for one that is
+    // already created for this request.
+    var length = context.spies.length;
+    for (var i=0; i<length; i++)
+    {
+        spy = context.spies[i];
+        if (spy.request == request)
+            return spy;
+    }
+
+    if (noCreate)
+        return null;
+
+    spy = new Firebug.Spy.XMLHttpRequestSpy(request, xhrRequest, context);
+    context.spies.push(spy);
+
+    var name = request.URI.asciiSpec;
+    var origName = request.originalURI.asciiSpec;
+
+    // Attach spy only to the original request. Notice that there can be more network requests
+    // made by the same XHR if redirects are involved.
+    if (name == origName)
+        spy.attach();
+
+    if (FBTrace.DBG_SPY)
+        FBTrace.sysout("spy.getSpyForXHR; New spy object created (" +
+            (name == origName ? "new XHR" : "redirected XHR") + ") for: " + name, spy);
+
+    return spy;
+}
+/**/
+
+// ************************************************************************************************
+
+/**
+ * @class This class represents a Spy object that is attached to XHR. This object
+ * registers various listeners into the XHR in order to monitor various events fired
+ * during the request process (onLoad, onAbort, etc.)
+ */
+/*
+Firebug.Spy.XMLHttpRequestSpy = function(request, xhrRequest, context)
+{
+    this.request = request;
+    this.xhrRequest = xhrRequest;
+    this.context = context;
+    this.responseText = "";
+
+    // For compatibility with the Net templates.
+    this.isXHR = true;
+
+    // Support for activity-observer
+    this.transactionStarted = false;
+    this.transactionClosed = false;
+};
+/**/
+
+//Firebug.Spy.XMLHttpRequestSpy.prototype =
+/** @lends Firebug.Spy.XMLHttpRequestSpy */
+/*
+{
+    attach: function()
+    {
+        var spy = this;
+        this.onReadyStateChange = function(event) { onHTTPSpyReadyStateChange(spy, event); };
+        this.onLoad = function() { onHTTPSpyLoad(spy); };
+        this.onError = function() { onHTTPSpyError(spy); };
+        this.onAbort = function() { onHTTPSpyAbort(spy); };
+
+        // xxxHonza: #502959 is still failing on Fx 3.5
+        // Use activity distributor to identify 3.6
+        if (SpyHttpActivityObserver.getActivityDistributor())
+        {
+            this.onreadystatechange = this.xhrRequest.onreadystatechange;
+            this.xhrRequest.onreadystatechange = this.onReadyStateChange;
+        }
+
+        this.xhrRequest.addEventListener("load", this.onLoad, false);
+        this.xhrRequest.addEventListener("error", this.onError, false);
+        this.xhrRequest.addEventListener("abort", this.onAbort, false);
+
+        // xxxHonza: should be removed from FB 3.6
+        if (!SpyHttpActivityObserver.getActivityDistributor())
+            this.context.sourceCache.addListener(this);
+    },
+
+    detach: function()
+    {
+        // Bubble out if already detached.
+        if (!this.onLoad)
+            return;
+
+        // If the activity distributor is available, let's detach it when the XHR
+        // transaction is closed. Since, in case of multipart XHRs the onLoad method
+        // (readyState == 4) can be called mutliple times.
+        // Keep in mind:
+        // 1) It can happen that that the TRANSACTION_CLOSE event comes before
+        // the onLoad (if the XHR is made as part of the page load) so, detach if
+        // it's already closed.
+        // 2) In case of immediate cache responses, the transaction doesn't have to
+        // be started at all (or the activity observer is no available in Firefox 3.5).
+        // So, also detach in this case.
+        if (this.transactionStarted && !this.transactionClosed)
+            return;
+
+        if (FBTrace.DBG_SPY)
+            FBTrace.sysout("spy.detach; " + this.href);
+
+        // Remove itself from the list of active spies.
+        remove(this.context.spies, this);
+
+        if (this.onreadystatechange)
+            this.xhrRequest.onreadystatechange = this.onreadystatechange;
+
+        try { this.xhrRequest.removeEventListener("load", this.onLoad, false); } catch (e) {}
+        try { this.xhrRequest.removeEventListener("error", this.onError, false); } catch (e) {}
+        try { this.xhrRequest.removeEventListener("abort", this.onAbort, false); } catch (e) {}
+
+        this.onreadystatechange = null;
+        this.onLoad = null;
+        this.onError = null;
+        this.onAbort = null;
+
+        // xxxHonza: shouuld be removed from FB 1.6
+        if (!SpyHttpActivityObserver.getActivityDistributor())
+            this.context.sourceCache.removeListener(this);
+    },
+
+    getURL: function()
+    {
+        return this.xhrRequest.channel ? this.xhrRequest.channel.name : this.href;
+    },
+
+    // Cache listener
+    onStopRequest: function(context, request, responseText)
+    {
+        if (!responseText)
+            return;
+
+        if (request == this.request)
+            this.responseText = responseText;
+    },
+};
+/**/
+// ************************************************************************************************
+/*
+function onHTTPSpyReadyStateChange(spy, event)
+{
+    if (FBTrace.DBG_SPY)
+        FBTrace.sysout("spy.onHTTPSpyReadyStateChange " + spy.xhrRequest.readyState +
+            " (multipart: " + spy.xhrRequest.multipart + ")");
+
+    // Remember just in case spy is detached (readyState == 4).
+    var originalHandler = spy.onreadystatechange;
+
+    // Force response text to be updated in the UI (in case the console entry
+    // has been already expanded and the response tab selected).
+    if (spy.logRow && spy.xhrRequest.readyState >= 3)
+    {
+        var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
+        if (netInfoBox)
+        {
+            netInfoBox.htmlPresented = false;
+            netInfoBox.responsePresented = false;
+        }
+    }
+
+    // If the request is loading update the end time.
+    if (spy.xhrRequest.readyState == 3)
+    {
+        spy.responseTime = spy.endTime - spy.sendTime;
+        updateTime(spy);
+    }
+
+    // Request loaded. Get all the info from the request now, just in case the
+    // XHR would be aborted in the original onReadyStateChange handler.
+    if (spy.xhrRequest.readyState == 4)
+    {
+        // Cumulate response so, multipart response content is properly displayed.
+        if (SpyHttpActivityObserver.getActivityDistributor())
+            spy.responseText += spy.xhrRequest.responseText;
+        else
+        {
+            // xxxHonza: remove from FB 1.6
+            if (!spy.responseText)
+                spy.responseText = spy.xhrRequest.responseText;
+        }
+
+        // The XHR is loaded now (used also by the activity observer).
+        spy.loaded = true;
+
+        // Update UI.
+        updateHttpSpyInfo(spy);
+
+        // Notify Net pane about a request beeing loaded.
+        // xxxHonza: I don't think this is necessary.
+        var netProgress = spy.context.netProgress;
+        if (netProgress)
+            netProgress.post(netProgress.stopFile, [spy.request, spy.endTime, spy.postText, spy.responseText]);
+
+        // Notify registered listeners about finish of the XHR.
+        dispatch(Firebug.Spy.fbListeners, "onLoad", [spy.context, spy]);
+    }
+
+    // Pass the event to the original page handler.
+    callPageHandler(spy, event, originalHandler);
+}
+
+function onHTTPSpyLoad(spy)
+{
+    if (FBTrace.DBG_SPY)
+        FBTrace.sysout("spy.onHTTPSpyLoad: " + spy.href, spy);
+
+    // Detach must be done in onLoad (not in onreadystatechange) otherwise
+    // onAbort would not be handled.
+    spy.detach();
+
+    // xxxHonza: Still needed for Fx 3.5 (#502959)
+    if (!SpyHttpActivityObserver.getActivityDistributor())
+        onHTTPSpyReadyStateChange(spy, null);
+}
+
+function onHTTPSpyError(spy)
+{
+    if (FBTrace.DBG_SPY)
+        FBTrace.sysout("spy.onHTTPSpyError; " + spy.href, spy);
+
+    spy.detach();
+    spy.loaded = true;
+
+    if (spy.logRow)
+    {
+        removeClass(spy.logRow, "loading");
+        setClass(spy.logRow, "error");
+    }
+}
+
+function onHTTPSpyAbort(spy)
+{
+    if (FBTrace.DBG_SPY)
+        FBTrace.sysout("spy.onHTTPSpyAbort: " + spy.href, spy);
+
+    spy.detach();
+    spy.loaded = true;
+
+    if (spy.logRow)
+    {
+        removeClass(spy.logRow, "loading");
+        setClass(spy.logRow, "error");
+    }
+
+    spy.statusText = "Aborted";
+    updateLogRow(spy);
+
+    // Notify Net pane about a request beeing aborted.
+    // xxxHonza: the net panel shoud find out this itself.
+    var netProgress = spy.context.netProgress;
+    if (netProgress)
+        netProgress.post(netProgress.abortFile, [spy.request, spy.endTime, spy.postText, spy.responseText]);
+}
+/**/
+
+// ************************************************************************************************
+
+/**
+ * @domplate Represents a template for XHRs logged in the Console panel. The body of the
+ * log (displayed when expanded) is rendered using {@link Firebug.NetMonitor.NetInfoBody}.
+ */
+
+Firebug.Spy.XHR = domplate(Firebug.Rep,
+/** @lends Firebug.Spy.XHR */
+
+{
+    tag:
+        DIV({"class": "spyHead", _repObject: "$object"},
+            TABLE({"class": "spyHeadTable focusRow outerFocusRow", cellpadding: 0, cellspacing: 0,
+                "role": "listitem", "aria-expanded": "false"},
+                TBODY({"role": "presentation"},
+                    TR({"class": "spyRow"},
+                        TD({"class": "spyTitleCol spyCol", onclick: "$onToggleBody"},
+                            DIV({"class": "spyTitle"},
+                                "$object|getCaption"
+                            ),
+                            DIV({"class": "spyFullTitle spyTitle"},
+                                "$object|getFullUri"
+                            )
+                        ),
+                        TD({"class": "spyCol"},
+                            DIV({"class": "spyStatus"}, "$object|getStatus")
+                        ),
+                        TD({"class": "spyCol"},
+                            SPAN({"class": "spyIcon"})
+                        ),
+                        TD({"class": "spyCol"},
+                            SPAN({"class": "spyTime"})
+                        ),
+                        TD({"class": "spyCol"},
+                            TAG(FirebugReps.SourceLink.tag, {object: "$object.sourceLink"})
+                        )
+                    )
+                )
+            )
+        ),
+
+    getCaption: function(spy)
+    {
+        return spy.method.toUpperCase() + " " + cropString(spy.getURL(), 100);
+    },
+
+    getFullUri: function(spy)
+    {
+        return spy.method.toUpperCase() + " " + spy.getURL();
+    },
+
+    getStatus: function(spy)
+    {
+        var text = "";
+        if (spy.statusCode)
+            text += spy.statusCode + " ";
+
+        if (spy.statusText)
+            return text += spy.statusText;
+
+        return text;
+    },
+
+    onToggleBody: function(event)
+    {
+        var target = event.currentTarget || event.srcElement;
+        var logRow = getAncestorByClass(target, "logRow-spy");
+
+        if (isLeftClick(event))
+        {
+            toggleClass(logRow, "opened");
+
+            var spy = getChildByClass(logRow, "spyHead").repObject;
+            var spyHeadTable = getAncestorByClass(target, "spyHeadTable");
+
+            if (hasClass(logRow, "opened"))
+            {
+                updateHttpSpyInfo(spy, logRow);
+                if (spyHeadTable)
+                    spyHeadTable.setAttribute('aria-expanded', 'true');
+            }
+            else
+            {
+                //var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
+                //dispatch(Firebug.NetMonitor.NetInfoBody.fbListeners, "destroyTabBody", [netInfoBox, spy]);
+                //if (spyHeadTable)
+                //    spyHeadTable.setAttribute('aria-expanded', 'false');
+            }
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    copyURL: function(spy)
+    {
+        copyToClipboard(spy.getURL());
+    },
+
+    copyParams: function(spy)
+    {
+        var text = spy.postText;
+        if (!text)
+            return;
+
+        var url = reEncodeURL(spy, text, true);
+        copyToClipboard(url);
+    },
+
+    copyResponse: function(spy)
+    {
+        copyToClipboard(spy.responseText);
+    },
+
+    openInTab: function(spy)
+    {
+        openNewTab(spy.getURL(), spy.postText);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    supportsObject: function(object)
+    {
+        // TODO: xxxpedro spy xhr
+        return false;
+
+        return object instanceof Firebug.Spy.XMLHttpRequestSpy;
+    },
+
+    browseObject: function(spy, context)
+    {
+        var url = spy.getURL();
+        openNewTab(url);
+        return true;
+    },
+
+    getRealObject: function(spy, context)
+    {
+        return spy.xhrRequest;
+    },
+
+    getContextMenuItems: function(spy)
+    {
+        var items = [
+            {label: "CopyLocation", command: bindFixed(this.copyURL, this, spy) }
+        ];
+
+        if (spy.postText)
+        {
+            items.push(
+                {label: "CopyLocationParameters", command: bindFixed(this.copyParams, this, spy) }
+            );
+        }
+
+        items.push(
+            {label: "CopyResponse", command: bindFixed(this.copyResponse, this, spy) },
+            "-",
+            {label: "OpenInTab", command: bindFixed(this.openInTab, this, spy) }
+        );
+
+        return items;
+    }
+});
+
+// ************************************************************************************************
+
+function updateTime(spy)
+{
+    var timeBox = spy.logRow.getElementsByClassName("spyTime").item(0);
+    if (spy.responseTime)
+        timeBox.textContent = " " + formatTime(spy.responseTime);
+}
+
+function updateLogRow(spy)
+{
+    updateTime(spy);
+
+    var statusBox = spy.logRow.getElementsByClassName("spyStatus").item(0);
+    statusBox.textContent = Firebug.Spy.XHR.getStatus(spy);
+
+    removeClass(spy.logRow, "loading");
+    setClass(spy.logRow, "loaded");
+
+    try
+    {
+        var errorRange = Math.floor(spy.xhrRequest.status/100);
+        if (errorRange == 4 || errorRange == 5)
+            setClass(spy.logRow, "error");
+    }
+    catch (exc)
+    {
+    }
+}
+
+var updateHttpSpyInfo = function updateHttpSpyInfo(spy, logRow)
+{
+    if (!spy.logRow && logRow)
+        spy.logRow = logRow;
+
+    if (!spy.logRow || !hasClass(spy.logRow, "opened"))
+        return;
+
+    if (!spy.params)
+        //spy.params = parseURLParams(spy.href+"");
+        spy.params = parseURLParams(spy.href+"");
+
+    if (!spy.requestHeaders)
+        spy.requestHeaders = getRequestHeaders(spy);
+
+    if (!spy.responseHeaders && spy.loaded)
+        spy.responseHeaders = getResponseHeaders(spy);
+
+    var template = Firebug.NetMonitor.NetInfoBody;
+    var netInfoBox = getChildByClass(spy.logRow, "spyHead", "netInfoBody");
+    if (!netInfoBox)
+    {
+        var head = getChildByClass(spy.logRow, "spyHead");
+        netInfoBox = template.tag.append({"file": spy}, head);
+        dispatch(template.fbListeners, "initTabBody", [netInfoBox, spy]);
+        template.selectTabByName(netInfoBox, "Response");
+    }
+    else
+    {
+        template.updateInfo(netInfoBox, spy, spy.context);
+    }
+};
+
+
+
+// ************************************************************************************************
+
+function getRequestHeaders(spy)
+{
+    var headers = [];
+
+    var channel = spy.xhrRequest.channel;
+    if (channel instanceof Ci.nsIHttpChannel)
+    {
+        channel.visitRequestHeaders({
+            visitHeader: function(name, value)
+            {
+                headers.push({name: name, value: value});
+            }
+        });
+    }
+
+    return headers;
+}
+
+function getResponseHeaders(spy)
+{
+    var headers = [];
+
+    try
+    {
+        var channel = spy.xhrRequest.channel;
+        if (channel instanceof Ci.nsIHttpChannel)
+        {
+            channel.visitResponseHeaders({
+                visitHeader: function(name, value)
+                {
+                    headers.push({name: name, value: value});
+                }
+            });
+        }
+    }
+    catch (exc)
+    {
+        if (FBTrace.DBG_SPY || FBTrace.DBG_ERRORS)
+            FBTrace.sysout("spy.getResponseHeaders; EXCEPTION " +
+                safeGetRequestName(spy.request), exc);
+    }
+
+    return headers;
+}
+
+// ************************************************************************************************
+// Registration
+
+Firebug.registerModule(Firebug.Spy);
+//Firebug.registerRep(Firebug.Spy.XHR);
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+
+// List of JSON content types.
+var contentTypes =
+{
+    // TODO: create issue: jsonViewer will not try to evaluate the contents of the requested file
+    // if the content-type is set to "text/plain"
+    //"text/plain": 1,
+    "text/javascript": 1,
+    "text/x-javascript": 1,
+    "text/json": 1,
+    "text/x-json": 1,
+    "application/json": 1,
+    "application/x-json": 1,
+    "application/javascript": 1,
+    "application/x-javascript": 1,
+    "application/json-rpc": 1
+};
+
+// ************************************************************************************************
+// Model implementation
+
+Firebug.JSONViewerModel = extend(Firebug.Module,
+{
+    dispatchName: "jsonViewer",
+    initialize: function()
+    {
+        Firebug.NetMonitor.NetInfoBody.addListener(this);
+
+        // Used by Firebug.DOMPanel.DirTable domplate.
+        this.toggles = {};
+    },
+
+    shutdown: function()
+    {
+        Firebug.NetMonitor.NetInfoBody.removeListener(this);
+    },
+
+    initTabBody: function(infoBox, file)
+    {
+        if (FBTrace.DBG_JSONVIEWER)
+            FBTrace.sysout("jsonviewer.initTabBody", infoBox);
+
+        // Let listeners to parse the JSON.
+        dispatch(this.fbListeners, "onParseJSON", [file]);
+
+        // The JSON is still no there, try to parse most common cases.
+        if (!file.jsonObject)
+        {
+            ///if (this.isJSON(safeGetContentType(file.request), file.responseText))
+            if (this.isJSON(file.mimeType, file.responseText))
+                file.jsonObject = this.parseJSON(file);
+        }
+
+        // The jsonObject is created so, the JSON tab can be displayed.
+        if (file.jsonObject && hasProperties(file.jsonObject))
+        {
+            Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "JSON",
+                ///$STR("jsonviewer.tab.JSON"));
+                $STR("JSON"));
+
+            if (FBTrace.DBG_JSONVIEWER)
+                FBTrace.sysout("jsonviewer.initTabBody; JSON object available " +
+                    (typeof(file.jsonObject) != "undefined"), file.jsonObject);
+        }
+    },
+
+    isJSON: function(contentType, data)
+    {
+        // Workaround for JSON responses without proper content type
+        // Let's consider all responses starting with "{" as JSON. In the worst
+        // case there will be an exception when parsing. This means that no-JSON
+        // responses (and post data) (with "{") can be parsed unnecessarily,
+        // which represents a little overhead, but this happens only if the request
+        // is actually expanded by the user in the UI (Net & Console panels).
+
+        ///var responseText = data ? trimLeft(data) : null;
+        ///if (responseText && responseText.indexOf("{") == 0)
+        ///    return true;
+        var responseText = data ? trim(data) : null;
+        if (responseText && responseText.indexOf("{") == 0)
+            return true;
+
+        if (!contentType)
+            return false;
+
+        contentType = contentType.split(";")[0];
+        contentType = trim(contentType);
+        return contentTypes[contentType];
+    },
+
+    // Update listener for TabView
+    updateTabBody: function(infoBox, file, context)
+    {
+        var tab = infoBox.selectedTab;
+        ///var tabBody = infoBox.getElementsByClassName("netInfoJSONText").item(0);
+        var tabBody = $$(".netInfoJSONText", infoBox)[0];
+        if (!hasClass(tab, "netInfoJSONTab") || tabBody.updated)
+            return;
+
+        tabBody.updated = true;
+
+        if (file.jsonObject) {
+            Firebug.DOMPanel.DirTable.tag.replace(
+                 {object: file.jsonObject, toggles: this.toggles}, tabBody);
+        }
+    },
+
+    parseJSON: function(file)
+    {
+        var jsonString = new String(file.responseText);
+        ///return parseJSONString(jsonString, "http://" + file.request.originalURI.host);
+        return parseJSONString(jsonString);
+    }
+});
+
+// ************************************************************************************************
+// Registration
+
+Firebug.registerModule(Firebug.JSONViewerModel);
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// Constants
+
+// List of XML related content types.
+var xmlContentTypes =
+[
+    "text/xml",
+    "application/xml",
+    "application/xhtml+xml",
+    "application/rss+xml",
+    "application/atom+xml",,
+    "application/vnd.mozilla.maybe.feed",
+    "application/rdf+xml",
+    "application/vnd.mozilla.xul+xml"
+];
+
+// ************************************************************************************************
+// Model implementation
+
+/**
+ * @module Implements viewer for XML based network responses. In order to create a new
+ * tab wihin network request detail, a listener is registered into
+ * <code>Firebug.NetMonitor.NetInfoBody</code> object.
+ */
+Firebug.XMLViewerModel = extend(Firebug.Module,
+{
+    dispatchName: "xmlViewer",
+
+    initialize: function()
+    {
+        ///Firebug.ActivableModule.initialize.apply(this, arguments);
+        Firebug.Module.initialize.apply(this, arguments);
+        Firebug.NetMonitor.NetInfoBody.addListener(this);
+    },
+
+    shutdown: function()
+    {
+        ///Firebug.ActivableModule.shutdown.apply(this, arguments);
+        Firebug.Module.shutdown.apply(this, arguments);
+        Firebug.NetMonitor.NetInfoBody.removeListener(this);
+    },
+
+    /**
+     * Check response's content-type and if it's a XML, create a new tab with XML preview.
+     */
+    initTabBody: function(infoBox, file)
+    {
+        if (FBTrace.DBG_XMLVIEWER)
+            FBTrace.sysout("xmlviewer.initTabBody", infoBox);
+
+        // If the response is XML let's display a pretty preview.
+        ///if (this.isXML(safeGetContentType(file.request)))
+        if (this.isXML(file.mimeType, file.responseText))
+        {
+            Firebug.NetMonitor.NetInfoBody.appendTab(infoBox, "XML",
+                ///$STR("xmlviewer.tab.XML"));
+                $STR("XML"));
+
+            if (FBTrace.DBG_XMLVIEWER)
+                FBTrace.sysout("xmlviewer.initTabBody; XML response available");
+        }
+    },
+
+    isXML: function(contentType)
+    {
+        if (!contentType)
+            return false;
+
+        // Look if the response is XML based.
+        for (var i=0; i<xmlContentTypes.length; i++)
+        {
+            if (contentType.indexOf(xmlContentTypes[i]) == 0)
+                return true;
+        }
+
+        return false;
+    },
+
+    /**
+     * Parse XML response and render pretty printed preview.
+     */
+    updateTabBody: function(infoBox, file, context)
+    {
+        var tab = infoBox.selectedTab;
+        ///var tabBody = infoBox.getElementsByClassName("netInfoXMLText").item(0);
+        var tabBody = $$(".netInfoXMLText", infoBox)[0];
+        if (!hasClass(tab, "netInfoXMLTab") || tabBody.updated)
+            return;
+
+        tabBody.updated = true;
+
+        this.insertXML(tabBody, Firebug.NetMonitor.Utils.getResponseText(file, context));
+    },
+
+    insertXML: function(parentNode, text)
+    {
+        var xmlText = text.replace(/^\s*<?.+?>\s*/, "");
+
+        var div = parentNode.ownerDocument.createElement("div");
+        div.innerHTML = xmlText;
+
+        var root = div.getElementsByTagName("*")[0];
+
+        /***
+        var parser = CCIN("@mozilla.org/xmlextras/domparser;1", "nsIDOMParser");
+        var doc = parser.parseFromString(text, "text/xml");
+        var root = doc.documentElement;
+
+        // Error handling
+        var nsURI = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
+        if (root.namespaceURI == nsURI && root.nodeName == "parsererror")
+        {
+            this.ParseError.tag.replace({error: {
+                message: root.firstChild.nodeValue,
+                source: root.lastChild.textContent
+            }}, parentNode);
+            return;
+        }
+        /**/
+
+        if (FBTrace.DBG_XMLVIEWER)
+            FBTrace.sysout("xmlviewer.updateTabBody; XML response parsed", doc);
+
+        // Override getHidden in these templates. The parsed XML documen is
+        // hidden, but we want to display it using 'visible' styling.
+        /*
+        var templates = [
+            Firebug.HTMLPanel.CompleteElement,
+            Firebug.HTMLPanel.Element,
+            Firebug.HTMLPanel.TextElement,
+            Firebug.HTMLPanel.EmptyElement,
+            Firebug.HTMLPanel.XEmptyElement,
+        ];
+
+        var originals = [];
+        for (var i=0; i<templates.length; i++)
+        {
+            originals[i] = templates[i].getHidden;
+            templates[i].getHidden = function() {
+                return "";
+            }
+        }
+        /**/
+
+        // Generate XML preview.
+        ///Firebug.HTMLPanel.CompleteElement.tag.replace({object: doc.documentElement}, parentNode);
+
+        // TODO: xxxpedro html3
+        ///Firebug.HTMLPanel.CompleteElement.tag.replace({object: root}, parentNode);
+        var html = [];
+        Firebug.Reps.appendNode(root, html);
+        parentNode.innerHTML = html.join("");
+
+
+        /*
+        for (var i=0; i<originals.length; i++)
+            templates[i].getHidden = originals[i];/**/
+    }
+});
+
+// ************************************************************************************************
+// Domplate
+
+/**
+ * @domplate Represents a template for displaying XML parser errors. Used by
+ * <code>Firebug.XMLViewerModel</code>.
+ */
+Firebug.XMLViewerModel.ParseError = domplate(Firebug.Rep,
+{
+    tag:
+        DIV({"class": "xmlInfoError"},
+            DIV({"class": "xmlInfoErrorMsg"}, "$error.message"),
+            PRE({"class": "xmlInfoErrorSource"}, "$error|getSource")
+        ),
+
+    getSource: function(error)
+    {
+        var parts = error.source.split("\n");
+        if (parts.length != 2)
+            return error.source;
+
+        var limit = 50;
+        var column = parts[1].length;
+        if (column >= limit) {
+            parts[0] = "..." + parts[0].substr(column - limit);
+            parts[1] = "..." + parts[1].substr(column - limit);
+        }
+
+        if (parts[0].length > 80)
+            parts[0] = parts[0].substr(0, 80) + "...";
+
+        return parts.join("\n");
+    }
+});
+
+// ************************************************************************************************
+// Registration
+
+Firebug.registerModule(Firebug.XMLViewerModel);
+
+}});
+
+
+/* See license.txt for terms of usage */
+
+// next-generation Console Panel (will override consoje.js)
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Constants
+
+/*
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const nsIPrefBranch2 = Ci.nsIPrefBranch2;
+const PrefService = Cc["@mozilla.org/preferences-service;1"];
+const prefs = PrefService.getService(nsIPrefBranch2);
+/**/
+/*
+
+// new offline message handler
+o = {x:1,y:2};
+
+r = Firebug.getRep(o);
+
+r.tag.tag.compile();
+
+outputs = [];
+html = r.tag.renderHTML({object:o}, outputs);
+
+
+// finish rendering the template (the DOM part)
+target = $("build");
+target.innerHTML = html;
+root = target.firstChild;
+
+domArgs = [root, r.tag.context, 0];
+domArgs.push.apply(domArgs, r.tag.domArgs);
+domArgs.push.apply(domArgs, outputs);
+r.tag.tag.renderDOM.apply(self ? self : r.tag.subject, domArgs);
+
+
+ */
+var consoleQueue = [];
+var lastHighlightedObject;
+var FirebugContext = Env.browser;
+
+// ************************************************************************************************
+
+var maxQueueRequests = 500;
+
+// ************************************************************************************************
+
+Firebug.ConsoleBase =
+{
+    log: function(object, context, className, rep, noThrottle, sourceLink)
+    {
+        //dispatch(this.fbListeners,"log",[context, object, className, sourceLink]);
+        return this.logRow(appendObject, object, context, className, rep, sourceLink, noThrottle);
+    },
+
+    logFormatted: function(objects, context, className, noThrottle, sourceLink)
+    {
+        //dispatch(this.fbListeners,"logFormatted",[context, objects, className, sourceLink]);
+        return this.logRow(appendFormatted, objects, context, className, null, sourceLink, noThrottle);
+    },
+
+    openGroup: function(objects, context, className, rep, noThrottle, sourceLink, noPush)
+    {
+        return this.logRow(appendOpenGroup, objects, context, className, rep, sourceLink, noThrottle);
+    },
+
+    closeGroup: function(context, noThrottle)
+    {
+        return this.logRow(appendCloseGroup, null, context, null, null, null, noThrottle, true);
+    },
+
+    logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow)
+    {
+        // TODO: xxxpedro console console2
+        noThrottle = true; // xxxpedro forced because there is no TabContext yet
+
+        if (!context)
+            context = FirebugContext;
+
+        if (FBTrace.DBG_ERRORS && !context)
+            FBTrace.sysout("Console.logRow has no context, skipping objects", objects);
+
+        if (!context)
+            return;
+
+        if (noThrottle || !context)
+        {
+            var panel = this.getPanel(context);
+            if (panel)
+            {
+                var row = panel.append(appender, objects, className, rep, sourceLink, noRow);
+                var container = panel.panelNode;
+
+                // TODO: xxxpedro what is this? console console2
+                /*
+                var template = Firebug.NetMonitor.NetLimit;
+
+                while (container.childNodes.length > maxQueueRequests + 1)
+                {
+                    clearDomplate(container.firstChild.nextSibling);
+                    container.removeChild(container.firstChild.nextSibling);
+                    panel.limit.limitInfo.totalCount++;
+                    template.updateCounter(panel.limit);
+                }
+                dispatch([Firebug.A11yModel], "onLogRowCreated", [panel , row]);
+                /**/
+                return row;
+            }
+            else
+            {
+                consoleQueue.push([appender, objects, context, className, rep, sourceLink, noThrottle, noRow]);
+            }
+        }
+        else
+        {
+            if (!context.throttle)
+            {
+                //FBTrace.sysout("console.logRow has not context.throttle! ");
+                return;
+            }
+            var args = [appender, objects, context, className, rep, sourceLink, true, noRow];
+            context.throttle(this.logRow, this, args);
+        }
+    },
+
+    appendFormatted: function(args, row, context)
+    {
+        if (!context)
+            context = FirebugContext;
+
+        var panel = this.getPanel(context);
+        panel.appendFormatted(args, row);
+    },
+
+    clear: function(context)
+    {
+        if (!context)
+            //context = FirebugContext;
+            context = Firebug.context;
+
+        /*
+        if (context)
+            Firebug.Errors.clear(context);
+        /**/
+
+        var panel = this.getPanel(context, true);
+        if (panel)
+        {
+            panel.clear();
+        }
+    },
+
+    // Override to direct output to your panel
+    getPanel: function(context, noCreate)
+    {
+        //return context.getPanel("console", noCreate);
+        // TODO: xxxpedro console console2
+        return Firebug.chrome ? Firebug.chrome.getPanel("Console") : null;
+    }
+
+};
+
+// ************************************************************************************************
+
+//TODO: xxxpedro
+//var ActivableConsole = extend(Firebug.ActivableModule, Firebug.ConsoleBase);
+var ActivableConsole = extend(Firebug.ConsoleBase,
+{
+    isAlwaysEnabled: function()
+    {
+        return true;
+    }
+});
+
+Firebug.Console = Firebug.Console = extend(ActivableConsole,
+//Firebug.Console = extend(ActivableConsole,
+{
+    dispatchName: "console",
+
+    error: function()
+    {
+        Firebug.Console.logFormatted(arguments, Firebug.browser, "error");
+    },
+
+    flush: function()
+    {
+        dispatch(this.fbListeners,"flush",[]);
+
+        for (var i=0, length=consoleQueue.length; i<length; i++)
+        {
+            var args = consoleQueue[i];
+            this.logRow.apply(this, args);
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Module
+
+    showPanel: function(browser, panel)
+    {
+    },
+
+    getFirebugConsoleElement: function(context, win)
+    {
+        var element = win.document.getElementById("_firebugConsole");
+        if (!element)
+        {
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("getFirebugConsoleElement forcing element");
+            var elementForcer = "(function(){var r=null; try { r = window._getFirebugConsoleElement();}catch(exc){r=exc;} return r;})();";  // we could just add the elements here
+
+            if (context.stopped)
+                Firebug.Console.injector.evaluateConsoleScript(context);  // todo evaluate consoleForcer on stack
+            else
+                var r = Firebug.CommandLine.evaluateInWebPage(elementForcer, context, win);
+
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("getFirebugConsoleElement forcing element result "+r, r);
+
+            var element = win.document.getElementById("_firebugConsole");
+            if (!element) // elementForce fails
+            {
+                if (FBTrace.DBG_ERRORS) FBTrace.sysout("console.getFirebugConsoleElement: no _firebugConsole in win:", win);
+                Firebug.Console.logFormatted(["Firebug cannot find _firebugConsole element", r, win], context, "error", true);
+            }
+        }
+
+        return element;
+    },
+
+    isReadyElsePreparing: function(context, win) // this is the only code that should call injector.attachIfNeeded
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.isReadyElsePreparing, win is " +
+                (win?"an argument: ":"null, context.window: ") +
+                (win?win.location:context.window.location), (win?win:context.window));
+
+        if (win)
+            return this.injector.attachIfNeeded(context, win);
+        else
+        {
+            var attached = true;
+            for (var i = 0; i < context.windows.length; i++)
+                attached = attached && this.injector.attachIfNeeded(context, context.windows[i]);
+            // already in the list above attached = attached && this.injector.attachIfNeeded(context, context.window);
+            if (context.windows.indexOf(context.window) == -1)
+                FBTrace.sysout("isReadyElsePreparing ***************** context.window not in context.windows");
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("console.isReadyElsePreparing attached to "+context.windows.length+" and returns "+attached);
+            return attached;
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends ActivableModule
+
+    initialize: function()
+    {
+        this.panelName = "console";
+
+        //TODO: xxxpedro
+        //Firebug.ActivableModule.initialize.apply(this, arguments);
+        //Firebug.Debugger.addListener(this);
+    },
+
+    enable: function()
+    {
+        if (Firebug.Console.isAlwaysEnabled())
+            this.watchForErrors();
+    },
+
+    disable: function()
+    {
+        if (Firebug.Console.isAlwaysEnabled())
+            this.unwatchForErrors();
+    },
+
+    initContext: function(context, persistedState)
+    {
+        Firebug.ActivableModule.initContext.apply(this, arguments);
+        context.consoleReloadWarning = true;  // mark as need to warn.
+    },
+
+    loadedContext: function(context)
+    {
+        for (var url in context.sourceFileMap)
+            return;  // if there are any sourceFiles, then do nothing
+
+        // else we saw no JS, so the reload warning it not needed.
+        this.clearReloadWarning(context);
+    },
+
+    clearReloadWarning: function(context) // remove the warning about reloading.
+    {
+         if (context.consoleReloadWarning)
+         {
+             var panel = context.getPanel(this.panelName);
+             panel.clearReloadWarning();
+             delete context.consoleReloadWarning;
+         }
+    },
+
+    togglePersist: function(context)
+    {
+        var panel = context.getPanel(this.panelName);
+        panel.persistContent = panel.persistContent ? false : true;
+        Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", panel.persistContent);
+    },
+
+    showContext: function(browser, context)
+    {
+        Firebug.chrome.setGlobalAttribute("cmd_clearConsole", "disabled", !context);
+
+        Firebug.ActivableModule.showContext.apply(this, arguments);
+    },
+
+    destroyContext: function(context, persistedState)
+    {
+        Firebug.Console.injector.detachConsole(context, context.window);  // TODO iterate windows?
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onPanelEnable: function(panelName)
+    {
+        if (panelName != this.panelName)  // we don't care about other panels
+            return;
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.onPanelEnable**************");
+
+        this.watchForErrors();
+        Firebug.Debugger.addDependentModule(this); // we inject the console during JS compiles so we need jsd
+    },
+
+    onPanelDisable: function(panelName)
+    {
+        if (panelName != this.panelName)  // we don't care about other panels
+            return;
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.onPanelDisable**************");
+
+        Firebug.Debugger.removeDependentModule(this); // we inject the console during JS compiles so we need jsd
+        this.unwatchForErrors();
+
+        // Make sure possible errors coming from the page and displayed in the Firefox
+        // status bar are removed.
+        this.clear();
+    },
+
+    onSuspendFirebug: function()
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.onSuspendFirebug\n");
+        if (Firebug.Console.isAlwaysEnabled())
+            this.unwatchForErrors();
+    },
+
+    onResumeFirebug: function()
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.onResumeFirebug\n");
+        if (Firebug.Console.isAlwaysEnabled())
+            this.watchForErrors();
+    },
+
+    watchForErrors: function()
+    {
+        Firebug.Errors.checkEnabled();
+        $('fbStatusIcon').setAttribute("console", "on");
+    },
+
+    unwatchForErrors: function()
+    {
+        Firebug.Errors.checkEnabled();
+        $('fbStatusIcon').removeAttribute("console");
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Firebug.Debugger listener
+
+    onMonitorScript: function(context, frame)
+    {
+        Firebug.Console.log(frame, context);
+    },
+
+    onFunctionCall: function(context, frame, depth, calling)
+    {
+        if (calling)
+            Firebug.Console.openGroup([frame, "depth:"+depth], context);
+        else
+            Firebug.Console.closeGroup(context);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    logRow: function(appender, objects, context, className, rep, sourceLink, noThrottle, noRow)
+    {
+        if (!context)
+            context = FirebugContext;
+
+        if (FBTrace.DBG_WINDOWS && !context) FBTrace.sysout("Console.logRow: no context \n");
+
+        if (this.isAlwaysEnabled())
+            return Firebug.ConsoleBase.logRow.apply(this, arguments);
+    }
+});
+
+Firebug.ConsoleListener =
+{
+    log: function(context, object, className, sourceLink)
+    {
+    },
+
+    logFormatted: function(context, objects, className, sourceLink)
+    {
+    }
+};
+
+// ************************************************************************************************
+
+Firebug.ConsolePanel = function () {} // XXjjb attach Firebug so this panel can be extended.
+
+//TODO: xxxpedro
+//Firebug.ConsolePanel.prototype = extend(Firebug.ActivablePanel,
+Firebug.ConsolePanel.prototype = extend(Firebug.Panel,
+{
+    wasScrolledToBottom: false,
+    messageCount: 0,
+    lastLogTime: 0,
+    groups: null,
+    limit: null,
+
+    append: function(appender, objects, className, rep, sourceLink, noRow)
+    {
+        var container = this.getTopContainer();
+
+        if (noRow)
+        {
+            appender.apply(this, [objects]);
+        }
+        else
+        {
+            // xxxHonza: Don't update the this.wasScrolledToBottom flag now.
+            // At the beginning (when the first log is created) the isScrolledToBottom
+            // always returns true.
+            //if (this.panelNode.offsetHeight)
+            //    this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
+
+            var row = this.createRow("logRow", className);
+            appender.apply(this, [objects, row, rep]);
+
+            if (sourceLink)
+                FirebugReps.SourceLink.tag.append({object: sourceLink}, row);
+
+            container.appendChild(row);
+
+            this.filterLogRow(row, this.wasScrolledToBottom);
+
+            if (this.wasScrolledToBottom)
+                scrollToBottom(this.panelNode);
+
+            return row;
+        }
+    },
+
+    clear: function()
+    {
+        if (this.panelNode)
+        {
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("ConsolePanel.clear");
+            clearNode(this.panelNode);
+            this.insertLogLimit(this.context);
+        }
+    },
+
+    insertLogLimit: function()
+    {
+        // Create limit row. This row is the first in the list of entries
+        // and initially hidden. It's displayed as soon as the number of
+        // entries reaches the limit.
+        var row = this.createRow("limitRow");
+
+        var limitInfo = {
+            totalCount: 0,
+            limitPrefsTitle: $STRF("LimitPrefsTitle", [Firebug.prefDomain+".console.logLimit"])
+        };
+
+        //TODO: xxxpedro console net limit!?
+        return;
+        var netLimitRep = Firebug.NetMonitor.NetLimit;
+        var nodes = netLimitRep.createTable(row, limitInfo);
+
+        this.limit = nodes[1];
+
+        var container = this.panelNode;
+        container.insertBefore(nodes[0], container.firstChild);
+    },
+
+    insertReloadWarning: function()
+    {
+        // put the message in, we will clear if the window console is injected.
+        this.warningRow = this.append(appendObject, $STR("message.Reload to activate window console"), "info");
+    },
+
+    clearReloadWarning: function()
+    {
+        if (this.warningRow)
+        {
+            this.warningRow.parentNode.removeChild(this.warningRow);
+            delete this.warningRow;
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    appendObject: function(object, row, rep)
+    {
+        if (!rep)
+            rep = Firebug.getRep(object);
+        return rep.tag.append({object: object}, row);
+    },
+
+    appendFormatted: function(objects, row, rep)
+    {
+        if (!objects || !objects.length)
+            return;
+
+        function logText(text, row)
+        {
+            var node = row.ownerDocument.createTextNode(text);
+            row.appendChild(node);
+        }
+
+        var format = objects[0];
+        var objIndex = 0;
+
+        if (typeof(format) != "string")
+        {
+            format = "";
+            objIndex = -1;
+        }
+        else  // a string
+        {
+            if (objects.length === 1) // then we have only a string...
+            {
+                if (format.length < 1) { // ...and it has no characters.
+                    logText("(an empty string)", row);
+                    return;
+                }
+            }
+        }
+
+        var parts = parseFormat(format);
+        var trialIndex = objIndex;
+        for (var i= 0; i < parts.length; i++)
+        {
+            var part = parts[i];
+            if (part && typeof(part) == "object")
+            {
+                if (++trialIndex > objects.length)  // then too few parameters for format, assume unformatted.
+                {
+                    format = "";
+                    objIndex = -1;
+                    parts.length = 0;
+                    break;
+                }
+            }
+
+        }
+        for (var i = 0; i < parts.length; ++i)
+        {
+            var part = parts[i];
+            if (part && typeof(part) == "object")
+            {
+                var object = objects[++objIndex];
+                if (typeof(object) != "undefined")
+                    this.appendObject(object, row, part.rep);
+                else
+                    this.appendObject(part.type, row, FirebugReps.Text);
+            }
+            else
+                FirebugReps.Text.tag.append({object: part}, row);
+        }
+
+        for (var i = objIndex+1; i < objects.length; ++i)
+        {
+            logText(" ", row);
+            var object = objects[i];
+            if (typeof(object) == "string")
+                FirebugReps.Text.tag.append({object: object}, row);
+            else
+                this.appendObject(object, row);
+        }
+    },
+
+    appendOpenGroup: function(objects, row, rep)
+    {
+        if (!this.groups)
+            this.groups = [];
+
+        setClass(row, "logGroup");
+        setClass(row, "opened");
+
+        var innerRow = this.createRow("logRow");
+        setClass(innerRow, "logGroupLabel");
+        if (rep)
+            rep.tag.replace({"objects": objects}, innerRow);
+        else
+            this.appendFormatted(objects, innerRow, rep);
+        row.appendChild(innerRow);
+        //dispatch([Firebug.A11yModel], 'onLogRowCreated', [this, innerRow]);
+        var groupBody = this.createRow("logGroupBody");
+        row.appendChild(groupBody);
+        groupBody.setAttribute('role', 'group');
+        this.groups.push(groupBody);
+
+        addEvent(innerRow, "mousedown", function(event)
+        {
+            if (isLeftClick(event))
+            {
+                //console.log(event.currentTarget == event.target);
+
+                var target = event.target || event.srcElement;
+
+                target = getAncestorByClass(target, "logGroupLabel");
+
+                var groupRow = target.parentNode;
+
+                if (hasClass(groupRow, "opened"))
+                {
+                    removeClass(groupRow, "opened");
+                    target.setAttribute('aria-expanded', 'false');
+                }
+                else
+                {
+                    setClass(groupRow, "opened");
+                    target.setAttribute('aria-expanded', 'true');
+                }
+            }
+        });
+    },
+
+    appendCloseGroup: function(object, row, rep)
+    {
+        if (this.groups)
+            this.groups.pop();
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // TODO: xxxpedro console2
+    onMouseMove: function(event)
+    {
+        if (!Firebug.Inspector) return;
+
+        var target = event.srcElement || event.target;
+
+        var object = getAncestorByClass(target, "objectLink-element");
+        object = object ? object.repObject : null;
+
+        if(object && instanceOf(object, "Element") && object.nodeType == 1)
+        {
+            if(object != lastHighlightedObject)
+            {
+                Firebug.Inspector.drawBoxModel(object);
+                object = lastHighlightedObject;
+            }
+        }
+        else
+            Firebug.Inspector.hideBoxModel();
+
+    },
+
+    onMouseDown: function(event)
+    {
+        var target = event.srcElement || event.target;
+
+        var object = getAncestorByClass(target, "objectLink");
+        var repObject = object ? object.repObject : null;
+
+        if (!repObject)
+        {
+            return;
+        }
+
+        if (hasClass(object, "objectLink-object"))
+        {
+            Firebug.chrome.selectPanel("DOM");
+            Firebug.chrome.getPanel("DOM").select(repObject, true);
+        }
+        else if (hasClass(object, "objectLink-element"))
+        {
+            Firebug.chrome.selectPanel("HTML");
+            Firebug.chrome.getPanel("HTML").select(repObject, true);
+        }
+
+        /*
+        if(object && instanceOf(object, "Element") && object.nodeType == 1)
+        {
+            if(object != lastHighlightedObject)
+            {
+                Firebug.Inspector.drawBoxModel(object);
+                object = lastHighlightedObject;
+            }
+        }
+        else
+            Firebug.Inspector.hideBoxModel();
+        /**/
+
+    },
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    name: "Console",
+    title: "Console",
+    //searchable: true,
+    //breakable: true,
+    //editable: false,
+
+    options:
+    {
+        hasCommandLine: true,
+        hasToolButtons: true,
+        isPreRendered: true
+    },
+
+    create: function()
+    {
+        Firebug.Panel.create.apply(this, arguments);
+
+        this.context = Firebug.browser.window;
+        this.document = Firebug.chrome.document;
+        this.onMouseMove = bind(this.onMouseMove, this);
+        this.onMouseDown = bind(this.onMouseDown, this);
+
+        this.clearButton = new Button({
+            element: $("fbConsole_btClear"),
+            owner: Firebug.Console,
+            onClick: Firebug.Console.clear
+        });
+    },
+
+    initialize: function()
+    {
+        Firebug.Panel.initialize.apply(this, arguments);  // loads persisted content
+        //Firebug.ActivablePanel.initialize.apply(this, arguments);  // loads persisted content
+
+        if (!this.persistedContent && Firebug.Console.isAlwaysEnabled())
+        {
+            this.insertLogLimit(this.context);
+
+            // Initialize log limit and listen for changes.
+            this.updateMaxLimit();
+
+            if (this.context.consoleReloadWarning)  // we have not yet injected the console
+                this.insertReloadWarning();
+        }
+
+        //Firebug.Console.injector.install(Firebug.browser.window);
+
+        addEvent(this.panelNode, "mouseover", this.onMouseMove);
+        addEvent(this.panelNode, "mousedown", this.onMouseDown);
+
+        this.clearButton.initialize();
+
+        //consolex.trace();
+        //TODO: xxxpedro remove this
+        /*
+        Firebug.Console.openGroup(["asd"], null, "group", null, false);
+        Firebug.Console.log("asd");
+        Firebug.Console.log("asd");
+        Firebug.Console.log("asd");
+        /**/
+
+        //TODO: xxxpedro preferences prefs
+        //prefs.addObserver(Firebug.prefDomain, this, false);
+    },
+
+    initializeNode : function()
+    {
+        //dispatch([Firebug.A11yModel], 'onInitializeNode', [this]);
+        if (FBTrace.DBG_CONSOLE)
+        {
+            this.onScroller = bind(this.onScroll, this);
+            addEvent(this.panelNode, "scroll", this.onScroller);
+        }
+
+        this.onResizer = bind(this.onResize, this);
+        this.resizeEventTarget = Firebug.chrome.$('fbContentBox');
+        addEvent(this.resizeEventTarget, "resize", this.onResizer);
+    },
+
+    destroyNode : function()
+    {
+        //dispatch([Firebug.A11yModel], 'onDestroyNode', [this]);
+        if (this.onScroller)
+            removeEvent(this.panelNode, "scroll", this.onScroller);
+
+        //removeEvent(this.resizeEventTarget, "resize", this.onResizer);
+    },
+
+    shutdown: function()
+    {
+        //TODO: xxxpedro console console2
+        this.clearButton.shutdown();
+
+        removeEvent(this.panelNode, "mousemove", this.onMouseMove);
+        removeEvent(this.panelNode, "mousedown", this.onMouseDown);
+
+        this.destroyNode();
+
+        Firebug.Panel.shutdown.apply(this, arguments);
+
+        //TODO: xxxpedro preferences prefs
+        //prefs.removeObserver(Firebug.prefDomain, this, false);
+    },
+
+    ishow: function(state)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("Console.panel show; " + this.context.getName(), state);
+
+        var enabled = Firebug.Console.isAlwaysEnabled();
+        if (enabled)
+        {
+             Firebug.Console.disabledPanelPage.hide(this);
+             this.showCommandLine(true);
+             this.showToolbarButtons("fbConsoleButtons", true);
+             Firebug.chrome.setGlobalAttribute("cmd_togglePersistConsole", "checked", this.persistContent);
+
+             if (state && state.wasScrolledToBottom)
+             {
+                 this.wasScrolledToBottom = state.wasScrolledToBottom;
+                 delete state.wasScrolledToBottom;
+             }
+
+             if (this.wasScrolledToBottom)
+                 scrollToBottom(this.panelNode);
+
+             if (FBTrace.DBG_CONSOLE)
+                 FBTrace.sysout("console.show ------------------ wasScrolledToBottom: " +
+                    this.wasScrolledToBottom + ", " + this.context.getName());
+        }
+        else
+        {
+            this.hide(state);
+            Firebug.Console.disabledPanelPage.show(this);
+        }
+    },
+
+    ihide: function(state)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("Console.panel hide; " + this.context.getName(), state);
+
+        this.showToolbarButtons("fbConsoleButtons", false);
+        this.showCommandLine(false);
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.hide ------------------ wasScrolledToBottom: " +
+                this.wasScrolledToBottom + ", " + this.context.getName());
+    },
+
+    destroy: function(state)
+    {
+        if (this.panelNode.offsetHeight)
+            this.wasScrolledToBottom = isScrolledToBottom(this.panelNode);
+
+        if (state)
+            state.wasScrolledToBottom = this.wasScrolledToBottom;
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.destroy ------------------ wasScrolledToBottom: " +
+                this.wasScrolledToBottom + ", " + this.context.getName());
+    },
+
+    shouldBreakOnNext: function()
+    {
+        // xxxHonza: shouldn't the breakOnErrors be context related?
+        // xxxJJB, yes, but we can't support it because we can't yet tell
+        // which window the error is on.
+        return Firebug.getPref(Firebug.servicePrefDomain, "breakOnErrors");
+    },
+
+    getBreakOnNextTooltip: function(enabled)
+    {
+        return (enabled ? $STR("console.Disable Break On All Errors") :
+            $STR("console.Break On All Errors"));
+    },
+
+    enablePanel: function(module)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.ConsolePanel.enablePanel; " + this.context.getName());
+
+        Firebug.ActivablePanel.enablePanel.apply(this, arguments);
+
+        this.showCommandLine(true);
+
+        if (this.wasScrolledToBottom)
+            scrollToBottom(this.panelNode);
+    },
+
+    disablePanel: function(module)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.ConsolePanel.disablePanel; " + this.context.getName());
+
+        Firebug.ActivablePanel.disablePanel.apply(this, arguments);
+
+        this.showCommandLine(false);
+    },
+
+    getOptionsMenuItems: function()
+    {
+        return [
+            optionMenu("ShowJavaScriptErrors", "showJSErrors"),
+            optionMenu("ShowJavaScriptWarnings", "showJSWarnings"),
+            optionMenu("ShowCSSErrors", "showCSSErrors"),
+            optionMenu("ShowXMLErrors", "showXMLErrors"),
+            optionMenu("ShowXMLHttpRequests", "showXMLHttpRequests"),
+            optionMenu("ShowChromeErrors", "showChromeErrors"),
+            optionMenu("ShowChromeMessages", "showChromeMessages"),
+            optionMenu("ShowExternalErrors", "showExternalErrors"),
+            optionMenu("ShowNetworkErrors", "showNetworkErrors"),
+            this.getShowStackTraceMenuItem(),
+            this.getStrictOptionMenuItem(),
+            "-",
+            optionMenu("LargeCommandLine", "largeCommandLine")
+        ];
+    },
+
+    getShowStackTraceMenuItem: function()
+    {
+        var menuItem = serviceOptionMenu("ShowStackTrace", "showStackTrace");
+        if (FirebugContext && !Firebug.Debugger.isAlwaysEnabled())
+            menuItem.disabled = true;
+        return menuItem;
+    },
+
+    getStrictOptionMenuItem: function()
+    {
+        var strictDomain = "javascript.options";
+        var strictName = "strict";
+        var strictValue = prefs.getBoolPref(strictDomain+"."+strictName);
+        return {label: "JavascriptOptionsStrict", type: "checkbox", checked: strictValue,
+            command: bindFixed(Firebug.setPref, Firebug, strictDomain, strictName, !strictValue) };
+    },
+
+    getBreakOnMenuItems: function()
+    {
+        //xxxHonza: no BON options for now.
+        /*return [
+            optionMenu("console.option.Persist Break On Error", "persistBreakOnError")
+        ];*/
+       return [];
+    },
+
+    search: function(text)
+    {
+        if (!text)
+            return;
+
+        // Make previously visible nodes invisible again
+        if (this.matchSet)
+        {
+            for (var i in this.matchSet)
+                removeClass(this.matchSet[i], "matched");
+        }
+
+        this.matchSet = [];
+
+        function findRow(node) { return getAncestorByClass(node, "logRow"); }
+        var search = new TextSearch(this.panelNode, findRow);
+
+        var logRow = search.find(text);
+        if (!logRow)
+        {
+            dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, []]);
+            return false;
+        }
+        for (; logRow; logRow = search.findNext())
+        {
+            setClass(logRow, "matched");
+            this.matchSet.push(logRow);
+        }
+        dispatch([Firebug.A11yModel], 'onConsoleSearchMatchFound', [this, text, this.matchSet]);
+        return true;
+    },
+
+    breakOnNext: function(breaking)
+    {
+        Firebug.setPref(Firebug.servicePrefDomain, "breakOnErrors", breaking);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // private
+
+    createRow: function(rowName, className)
+    {
+        var elt = this.document.createElement("div");
+        elt.className = rowName + (className ? " " + rowName + "-" + className : "");
+        return elt;
+    },
+
+    getTopContainer: function()
+    {
+        if (this.groups && this.groups.length)
+            return this.groups[this.groups.length-1];
+        else
+            return this.panelNode;
+    },
+
+    filterLogRow: function(logRow, scrolledToBottom)
+    {
+        if (this.searchText)
+        {
+            setClass(logRow, "matching");
+            setClass(logRow, "matched");
+
+            // Search after a delay because we must wait for a frame to be created for
+            // the new logRow so that the finder will be able to locate it
+            setTimeout(bindFixed(function()
+            {
+                if (this.searchFilter(this.searchText, logRow))
+                    this.matchSet.push(logRow);
+                else
+                    removeClass(logRow, "matched");
+
+                removeClass(logRow, "matching");
+
+                if (scrolledToBottom)
+                    scrollToBottom(this.panelNode);
+            }, this), 100);
+        }
+    },
+
+    searchFilter: function(text, logRow)
+    {
+        var count = this.panelNode.childNodes.length;
+        var searchRange = this.document.createRange();
+        searchRange.setStart(this.panelNode, 0);
+        searchRange.setEnd(this.panelNode, count);
+
+        var startPt = this.document.createRange();
+        startPt.setStartBefore(logRow);
+
+        var endPt = this.document.createRange();
+        endPt.setStartAfter(logRow);
+
+        return finder.Find(text, searchRange, startPt, endPt) != null;
+    },
+
+    // nsIPrefObserver
+    observe: function(subject, topic, data)
+    {
+        // We're observing preferences only.
+        if (topic != "nsPref:changed")
+          return;
+
+        // xxxHonza check this out.
+        var prefDomain = "Firebug.extension.";
+        var prefName = data.substr(prefDomain.length);
+        if (prefName == "console.logLimit")
+            this.updateMaxLimit();
+    },
+
+    updateMaxLimit: function()
+    {
+        var value = 1000;
+        //TODO: xxxpedro preferences log limit?
+        //var value = Firebug.getPref(Firebug.prefDomain, "console.logLimit");
+        maxQueueRequests =  value ? value : maxQueueRequests;
+    },
+
+    showCommandLine: function(shouldShow)
+    {
+        //TODO: xxxpedro show command line important
+        return;
+
+        if (shouldShow)
+        {
+            collapse(Firebug.chrome.$("fbCommandBox"), false);
+            Firebug.CommandLine.setMultiLine(Firebug.largeCommandLine, Firebug.chrome);
+        }
+        else
+        {
+            // Make sure that entire content of the Console panel is hidden when
+            // the panel is disabled.
+            Firebug.CommandLine.setMultiLine(false, Firebug.chrome, Firebug.largeCommandLine);
+            collapse(Firebug.chrome.$("fbCommandBox"), true);
+        }
+    },
+
+    onScroll: function(event)
+    {
+        // Update the scroll position flag if the position changes.
+        this.wasScrolledToBottom = FBL.isScrolledToBottom(this.panelNode);
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.onScroll ------------------ wasScrolledToBottom: " +
+                this.wasScrolledToBottom + ", wasScrolledToBottom: " +
+                this.context.getName(), event);
+    },
+
+    onResize: function(event)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("console.onResize ------------------ wasScrolledToBottom: " +
+                this.wasScrolledToBottom + ", offsetHeight: " + this.panelNode.offsetHeight +
+                ", scrollTop: " + this.panelNode.scrollTop + ", scrollHeight: " +
+                this.panelNode.scrollHeight + ", " + this.context.getName(), event);
+
+        if (this.wasScrolledToBottom)
+            scrollToBottom(this.panelNode);
+    }
+});
+
+// ************************************************************************************************
+
+function parseFormat(format)
+{
+    var parts = [];
+    if (format.length <= 0)
+        return parts;
+
+    var reg = /((^%|.%)(\d+)?(\.)([a-zA-Z]))|((^%|.%)([a-zA-Z]))/;
+    for (var m = reg.exec(format); m; m = reg.exec(format))
+    {
+        if (m[0].substr(0, 2) == "%%")
+        {
+            parts.push(format.substr(0, m.index));
+            parts.push(m[0].substr(1));
+        }
+        else
+        {
+            var type = m[8] ? m[8] : m[5];
+            var precision = m[3] ? parseInt(m[3]) : (m[4] == "." ? -1 : 0);
+
+            var rep = null;
+            switch (type)
+            {
+                case "s":
+                    rep = FirebugReps.Text;
+                    break;
+                case "f":
+                case "i":
+                case "d":
+                    rep = FirebugReps.Number;
+                    break;
+                case "o":
+                    rep = null;
+                    break;
+            }
+
+            parts.push(format.substr(0, m[0][0] == "%" ? m.index : m.index+1));
+            parts.push({rep: rep, precision: precision, type: ("%" + type)});
+        }
+
+        format = format.substr(m.index+m[0].length);
+    }
+
+    parts.push(format);
+    return parts;
+}
+
+// ************************************************************************************************
+
+var appendObject = Firebug.ConsolePanel.prototype.appendObject;
+var appendFormatted = Firebug.ConsolePanel.prototype.appendFormatted;
+var appendOpenGroup = Firebug.ConsolePanel.prototype.appendOpenGroup;
+var appendCloseGroup = Firebug.ConsolePanel.prototype.appendCloseGroup;
+
+// ************************************************************************************************
+
+//Firebug.registerActivableModule(Firebug.Console);
+Firebug.registerModule(Firebug.Console);
+Firebug.registerPanel(Firebug.ConsolePanel);
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// Constants
+
+//const Cc = Components.classes;
+//const Ci = Components.interfaces;
+
+var frameCounters = {};
+var traceRecursion = 0;
+
+Firebug.Console.injector =
+{
+    install: function(context)
+    {
+        var win = context.window;
+
+        var consoleHandler = new FirebugConsoleHandler(context, win);
+
+        var properties =
+        [
+            "log",
+            "debug",
+            "info",
+            "warn",
+            "error",
+            "assert",
+            "dir",
+            "dirxml",
+            "group",
+            "groupCollapsed",
+            "groupEnd",
+            "time",
+            "timeEnd",
+            "count",
+            "trace",
+            "profile",
+            "profileEnd",
+            "clear",
+            "open",
+            "close"
+        ];
+
+        var Handler = function(name)
+        {
+            var c = consoleHandler;
+            var f = consoleHandler[name];
+            return function(){return f.apply(c,arguments);};
+        };
+
+        var installer = function(c)
+        {
+            for (var i=0, l=properties.length; i<l; i++)
+            {
+                var name = properties[i];
+                c[name] = new Handler(name);
+                c.firebuglite = Firebug.version;
+            }
+        };
+
+        var sandbox;
+
+        if (win.console)
+        {
+            if (Env.Options.overrideConsole)
+                sandbox = new win.Function("arguments.callee.install(window.console={})");
+            else
+                // if there's a console object and overrideConsole is false we should just quit
+                return;
+        }
+        else
+        {
+            try
+            {
+                // try overriding the console object
+                sandbox = new win.Function("arguments.callee.install(window.console={})");
+            }
+            catch(E)
+            {
+                // if something goes wrong create the firebug object instead
+                sandbox = new win.Function("arguments.callee.install(window.firebug={})");
+            }
+        }
+
+        sandbox.install = installer;
+        sandbox();
+    },
+
+    isAttached: function(context, win)
+    {
+        if (win.wrappedJSObject)
+        {
+            var attached = (win.wrappedJSObject._getFirebugConsoleElement ? true : false);
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("Console.isAttached:"+attached+" to win.wrappedJSObject "+safeGetWindowLocation(win.wrappedJSObject));
+
+            return attached;
+        }
+        else
+        {
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("Console.isAttached? to win "+win.location+" fnc:"+win._getFirebugConsoleElement);
+            return (win._getFirebugConsoleElement ? true : false);
+        }
+    },
+
+    attachIfNeeded: function(context, win)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("Console.attachIfNeeded has win "+(win? ((win.wrappedJSObject?"YES":"NO")+" wrappedJSObject"):"null") );
+
+        if (this.isAttached(context, win))
+            return true;
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("Console.attachIfNeeded found isAttached false ");
+
+        this.attachConsoleInjector(context, win);
+        this.addConsoleListener(context, win);
+
+        Firebug.Console.clearReloadWarning(context);
+
+        var attached =  this.isAttached(context, win);
+        if (attached)
+            dispatch(Firebug.Console.fbListeners, "onConsoleInjected", [context, win]);
+
+        return attached;
+    },
+
+    attachConsoleInjector: function(context, win)
+    {
+        var consoleInjection = this.getConsoleInjectionScript();  // Do it all here.
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("attachConsoleInjector evaluating in "+win.location, consoleInjection);
+
+        Firebug.CommandLine.evaluateInWebPage(consoleInjection, context, win);
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("attachConsoleInjector evaluation completed for "+win.location);
+    },
+
+    getConsoleInjectionScript: function() {
+        if (!this.consoleInjectionScript)
+        {
+            var script = "";
+            script += "window.__defineGetter__('console', function() {\n";
+            script += " return (window._firebug ? window._firebug : window.loadFirebugConsole()); })\n\n";
+
+            script += "window.loadFirebugConsole = function() {\n";
+            script += "window._firebug =  new _FirebugConsole();";
+
+            if (FBTrace.DBG_CONSOLE)
+                script += " window.dump('loadFirebugConsole '+window.location+'\\n');\n";
+
+            script += " return window._firebug };\n";
+
+            var theFirebugConsoleScript = getResource("chrome://firebug/content/consoleInjected.js");
+            script += theFirebugConsoleScript;
+
+
+            this.consoleInjectionScript = script;
+        }
+        return this.consoleInjectionScript;
+    },
+
+    forceConsoleCompilationInPage: function(context, win)
+    {
+        if (!win)
+        {
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("no win in forceConsoleCompilationInPage!");
+            return;
+        }
+
+        var consoleForcer = "window.loadFirebugConsole();";
+
+        if (context.stopped)
+            Firebug.Console.injector.evaluateConsoleScript(context);  // todo evaluate consoleForcer on stack
+        else
+            Firebug.CommandLine.evaluateInWebPage(consoleForcer, context, win);
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("forceConsoleCompilationInPage "+win.location, consoleForcer);
+    },
+
+    evaluateConsoleScript: function(context)
+    {
+        var scriptSource = this.getConsoleInjectionScript(); // TODO XXXjjb this should be getConsoleInjectionScript
+        Firebug.Debugger.evaluate(scriptSource, context);
+    },
+
+    addConsoleListener: function(context, win)
+    {
+        if (!context.activeConsoleHandlers)  // then we have not been this way before
+            context.activeConsoleHandlers = [];
+        else
+        {   // we've been this way before...
+            for (var i=0; i<context.activeConsoleHandlers.length; i++)
+            {
+                if (context.activeConsoleHandlers[i].window == win)
+                {
+                    context.activeConsoleHandlers[i].detach();
+                    if (FBTrace.DBG_CONSOLE)
+                        FBTrace.sysout("consoleInjector addConsoleListener removed handler("+context.activeConsoleHandlers[i].handler_name+") from _firebugConsole in : "+win.location+"\n");
+                    context.activeConsoleHandlers.splice(i,1);
+                }
+            }
+        }
+
+        // We need the element to attach our event listener.
+        var element = Firebug.Console.getFirebugConsoleElement(context, win);
+        if (element)
+            element.setAttribute("FirebugVersion", Firebug.version); // Initialize Firebug version.
+        else
+            return false;
+
+        var handler = new FirebugConsoleHandler(context, win);
+        handler.attachTo(element);
+
+        context.activeConsoleHandlers.push(handler);
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("consoleInjector addConsoleListener attached handler("+handler.handler_name+") to _firebugConsole in : "+win.location+"\n");
+        return true;
+    },
+
+    detachConsole: function(context, win)
+    {
+        if (win && win.document)
+        {
+            var element = win.document.getElementById("_firebugConsole");
+            if (element)
+                element.parentNode.removeChild(element);
+        }
+    }
+};
+
+var total_handlers = 0;
+var FirebugConsoleHandler = function FirebugConsoleHandler(context, win)
+{
+    this.window = win;
+
+    this.attachTo = function(element)
+    {
+        this.element = element;
+        // When raised on our injected element, callback to Firebug and append to console
+        this.boundHandler = bind(this.handleEvent, this);
+        this.element.addEventListener('firebugAppendConsole', this.boundHandler, true); // capturing
+    };
+
+    this.detach = function()
+    {
+        this.element.removeEventListener('firebugAppendConsole', this.boundHandler, true);
+    };
+
+    this.handler_name = ++total_handlers;
+    this.handleEvent = function(event)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("FirebugConsoleHandler("+this.handler_name+") "+event.target.getAttribute("methodName")+", event", event);
+        if (!Firebug.CommandLine.CommandHandler.handle(event, this, win))
+        {
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("FirebugConsoleHandler", this);
+
+            var methodName = event.target.getAttribute("methodName");
+            Firebug.Console.log($STRF("console.MethodNotSupported", [methodName]));
+        }
+    };
+
+    this.firebuglite = Firebug.version;
+
+    this.init = function()
+    {
+        var consoleElement = win.document.getElementById('_firebugConsole');
+        consoleElement.setAttribute("FirebugVersion", Firebug.version);
+    };
+
+    this.log = function()
+    {
+        logFormatted(arguments, "log");
+    };
+
+    this.debug = function()
+    {
+        logFormatted(arguments, "debug", true);
+    };
+
+    this.info = function()
+    {
+        logFormatted(arguments, "info", true);
+    };
+
+    this.warn = function()
+    {
+        logFormatted(arguments, "warn", true);
+    };
+
+    this.error = function()
+    {
+        //TODO: xxxpedro console error
+        //if (arguments.length == 1)
+        //{
+        //    logAssert("error", arguments);  // add more info based on stack trace
+        //}
+        //else
+        //{
+            //Firebug.Errors.increaseCount(context);
+            logFormatted(arguments, "error", true);  // user already added info
+        //}
+    };
+
+    this.exception = function()
+    {
+        logAssert("error", arguments);
+    };
+
+    this.assert = function(x)
+    {
+        if (!x)
+        {
+            var rest = [];
+            for (var i = 1; i < arguments.length; i++)
+                rest.push(arguments[i]);
+            logAssert("assert", rest);
+        }
+    };
+
+    this.dir = function(o)
+    {
+        Firebug.Console.log(o, context, "dir", Firebug.DOMPanel.DirTable);
+    };
+
+    this.dirxml = function(o)
+    {
+        ///if (o instanceof Window)
+        if (instanceOf(o, "Window"))
+            o = o.document.documentElement;
+        ///else if (o instanceof Document)
+        else if (instanceOf(o, "Document"))
+            o = o.documentElement;
+
+        Firebug.Console.log(o, context, "dirxml", Firebug.HTMLPanel.SoloElement);
+    };
+
+    this.group = function()
+    {
+        //TODO: xxxpedro;
+        //var sourceLink = getStackLink();
+        var sourceLink = null;
+        Firebug.Console.openGroup(arguments, null, "group", null, false, sourceLink);
+    };
+
+    this.groupEnd = function()
+    {
+        Firebug.Console.closeGroup(context);
+    };
+
+    this.groupCollapsed = function()
+    {
+        var sourceLink = getStackLink();
+        // noThrottle true is probably ok, openGroups will likely be short strings.
+        var row = Firebug.Console.openGroup(arguments, null, "group", null, true, sourceLink);
+        removeClass(row, "opened");
+    };
+
+    this.profile = function(title)
+    {
+        logFormatted(["console.profile() not supported."], "warn", true);
+
+        //Firebug.Profiler.startProfiling(context, title);
+    };
+
+    this.profileEnd = function()
+    {
+        logFormatted(["console.profile() not supported."], "warn", true);
+
+        //Firebug.Profiler.stopProfiling(context);
+    };
+
+    this.count = function(key)
+    {
+        // TODO: xxxpedro console2: is there a better way to find a unique ID for the coun() call?
+        var frameId = "0";
+        //var frameId = FBL.getStackFrameId();
+        if (frameId)
+        {
+            if (!frameCounters)
+                frameCounters = {};
+
+            if (key != undefined)
+                frameId += key;
+
+            var frameCounter = frameCounters[frameId];
+            if (!frameCounter)
+            {
+                var logRow = logFormatted(["0"], null, true, true);
+
+                frameCounter = {logRow: logRow, count: 1};
+                frameCounters[frameId] = frameCounter;
+            }
+            else
+                ++frameCounter.count;
+
+            var label = key == undefined
+                ? frameCounter.count
+                : key + " " + frameCounter.count;
+
+            frameCounter.logRow.firstChild.firstChild.nodeValue = label;
+        }
+    };
+
+    this.trace = function()
+    {
+        var getFuncName = function getFuncName (f)
+        {
+            if (f.getName instanceof Function)
+            {
+                return f.getName();
+            }
+            if (f.name) // in FireFox, Function objects have a name property...
+            {
+                return f.name;
+            }
+
+            var name = f.toString().match(/function\s*([_$\w\d]*)/)[1];
+            return name || "anonymous";
+        };
+
+        var wasVisited = function(fn)
+        {
+            for (var i=0, l=frames.length; i<l; i++)
+            {
+                if (frames[i].fn == fn)
+                {
+                    return true;
+                }
+            }
+
+            return false;
+        };
+
+        traceRecursion++;
+
+        if (traceRecursion > 1)
+        {
+            traceRecursion--;
+            return;
+        }
+
+        var frames = [];
+
+        for (var fn = arguments.callee.caller.caller; fn; fn = fn.caller)
+        {
+            if (wasVisited(fn)) break;
+
+            var args = [];
+
+            for (var i = 0, l = fn.arguments.length; i < l; ++i)
+            {
+                args.push({value: fn.arguments[i]});
+            }
+
+            frames.push({fn: fn, name: getFuncName(fn), args: args});
+        }
+
+
+        // ****************************************************************************************
+
+        try
+        {
+            (0)();
+        }
+        catch(e)
+        {
+            var result = e;
+
+            var stack =
+                result.stack || // Firefox / Google Chrome
+                result.stacktrace || // Opera
+                "";
+
+            stack = stack.replace(/\n\r|\r\n/g, "\n"); // normalize line breaks
+            var items = stack.split(/[\n\r]/);
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            // Google Chrome
+            if (FBL.isSafari)
+            {
+                //var reChromeStackItem = /^\s+at\s+([^\(]+)\s\((.*)\)$/;
+                //var reChromeStackItem = /^\s+at\s+(.*)((?:http|https|ftp|file):\/\/.*)$/;
+                var reChromeStackItem = /^\s+at\s+(.*)((?:http|https|ftp|file):\/\/.*)$/;
+
+                var reChromeStackItemName = /\s*\($/;
+                var reChromeStackItemValue = /^(.+)\:(\d+\:\d+)\)?$/;
+
+                var framePos = 0;
+                for (var i=4, length=items.length; i<length; i++, framePos++)
+                {
+                    var frame = frames[framePos];
+                    var item = items[i];
+                    var match = item.match(reChromeStackItem);
+
+                    //Firebug.Console.log("["+ framePos +"]--------------------------");
+                    //Firebug.Console.log(item);
+                    //Firebug.Console.log("................");
+
+                    if (match)
+                    {
+                        var name = match[1];
+                        if (name)
+                        {
+                            name = name.replace(reChromeStackItemName, "");
+                            frame.name = name;
+                        }
+
+                        //Firebug.Console.log("name: "+name);
+
+                        var value = match[2].match(reChromeStackItemValue);
+                        if (value)
+                        {
+                            frame.href = value[1];
+                            frame.lineNo = value[2];
+
+                            //Firebug.Console.log("url: "+value[1]);
+                            //Firebug.Console.log("line: "+value[2]);
+                        }
+                        //else
+                        //    Firebug.Console.log(match[2]);
+
+                    }
+                }
+            }
+            /**/
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            else if (FBL.isFirefox)
+            {
+                // Firefox
+                var reFirefoxStackItem = /^(.*)@(.*)$/;
+                var reFirefoxStackItemValue = /^(.+)\:(\d+)$/;
+
+                var framePos = 0;
+                for (var i=2, length=items.length; i<length; i++, framePos++)
+                {
+                    var frame = frames[framePos] || {};
+                    var item = items[i];
+                    var match = item.match(reFirefoxStackItem);
+
+                    if (match)
+                    {
+                        var name = match[1];
+
+                        //Firebug.Console.logFormatted("name: "+name);
+
+                        var value = match[2].match(reFirefoxStackItemValue);
+                        if (value)
+                        {
+                            frame.href = value[1];
+                            frame.lineNo = value[2];
+
+                            //Firebug.Console.log("href: "+ value[1]);
+                            //Firebug.Console.log("line: " + value[2]);
+                        }
+                        //else
+                        //    Firebug.Console.logFormatted([match[2]]);
+                    }
+                }
+            }
+            /**/
+
+            // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+            /*
+            else if (FBL.isOpera)
+            {
+                // Opera
+                var reOperaStackItem = /^\s\s(?:\.\.\.\s\s)?Line\s(\d+)\sof\s(.+)$/;
+                var reOperaStackItemValue = /^linked\sscript\s(.+)$/;
+
+                for (var i=0, length=items.length; i<length; i+=2)
+                {
+                    var item = items[i];
+
+                    var match = item.match(reOperaStackItem);
+
+                    if (match)
+                    {
+                        //Firebug.Console.log(match[1]);
+
+                        var value = match[2].match(reOperaStackItemValue);
+
+                        if (value)
+                        {
+                            //Firebug.Console.log(value[1]);
+                        }
+                        //else
+                        //    Firebug.Console.log(match[2]);
+
+                        //Firebug.Console.log("--------------------------");
+                    }
+                }
+            }
+            /**/
+        }
+
+        //console.log(stack);
+        //console.dir(frames);
+        Firebug.Console.log({frames: frames}, context, "stackTrace", FirebugReps.StackTrace);
+
+        traceRecursion--;
+    };
+
+    this.trace_ok = function()
+    {
+        var getFuncName = function getFuncName (f)
+        {
+            if (f.getName instanceof Function)
+                return f.getName();
+            if (f.name) // in FireFox, Function objects have a name property...
+                return f.name;
+
+            var name = f.toString().match(/function\s*([_$\w\d]*)/)[1];
+            return name || "anonymous";
+        };
+
+        var wasVisited = function(fn)
+        {
+            for (var i=0, l=frames.length; i<l; i++)
+            {
+                if (frames[i].fn == fn)
+                    return true;
+            }
+
+            return false;
+        };
+
+        var frames = [];
+
+        for (var fn = arguments.callee.caller; fn; fn = fn.caller)
+        {
+            if (wasVisited(fn)) break;
+
+            var args = [];
+
+            for (var i = 0, l = fn.arguments.length; i < l; ++i)
+            {
+                args.push({value: fn.arguments[i]});
+            }
+
+            frames.push({fn: fn, name: getFuncName(fn), args: args});
+        }
+
+        Firebug.Console.log({frames: frames}, context, "stackTrace", FirebugReps.StackTrace);
+    };
+
+    this.clear = function()
+    {
+        Firebug.Console.clear(context);
+    };
+
+    this.time = function(name, reset)
+    {
+        if (!name)
+            return;
+
+        var time = new Date().getTime();
+
+        if (!this.timeCounters)
+            this.timeCounters = {};
+
+        var key = "KEY"+name.toString();
+
+        if (!reset && this.timeCounters[key])
+            return;
+
+        this.timeCounters[key] = time;
+    };
+
+    this.timeEnd = function(name)
+    {
+        var time = new Date().getTime();
+
+        if (!this.timeCounters)
+            return;
+
+        var key = "KEY"+name.toString();
+
+        var timeCounter = this.timeCounters[key];
+        if (timeCounter)
+        {
+            var diff = time - timeCounter;
+            var label = name + ": " + diff + "ms";
+
+            this.info(label);
+
+            delete this.timeCounters[key];
+        }
+        return diff;
+    };
+
+    // These functions are over-ridden by commandLine
+    this.evaluated = function(result, context)
+    {
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("consoleInjector.FirebugConsoleHandler evalutated default called", result);
+
+        Firebug.Console.log(result, context);
+    };
+    this.evaluateError = function(result, context)
+    {
+        Firebug.Console.log(result, context, "errorMessage");
+    };
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    function logFormatted(args, className, linkToSource, noThrottle)
+    {
+        var sourceLink = linkToSource ? getStackLink() : null;
+        return Firebug.Console.logFormatted(args, context, className, noThrottle, sourceLink);
+    }
+
+    function logAssert(category, args)
+    {
+        Firebug.Errors.increaseCount(context);
+
+        if (!args || !args.length || args.length == 0)
+            var msg = [FBL.$STR("Assertion")];
+        else
+            var msg = args[0];
+
+        if (Firebug.errorStackTrace)
+        {
+            var trace = Firebug.errorStackTrace;
+            delete Firebug.errorStackTrace;
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("logAssert trace from errorStackTrace", trace);
+        }
+        else if (msg.stack)
+        {
+            var trace = parseToStackTrace(msg.stack);
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("logAssert trace from msg.stack", trace);
+        }
+        else
+        {
+            var trace = getJSDUserStack();
+            if (FBTrace.DBG_CONSOLE)
+                FBTrace.sysout("logAssert trace from getJSDUserStack", trace);
+        }
+
+        var errorObject = new FBL.ErrorMessage(msg, (msg.fileName?msg.fileName:win.location), (msg.lineNumber?msg.lineNumber:0), "", category, context, trace);
+
+
+        if (trace && trace.frames && trace.frames[0])
+           errorObject.correctWithStackTrace(trace);
+
+        errorObject.resetSource();
+
+        var objects = errorObject;
+        if (args.length > 1)
+        {
+            objects = [errorObject];
+            for (var i = 1; i < args.length; i++)
+                objects.push(args[i]);
+        }
+
+        var row = Firebug.Console.log(objects, context, "errorMessage", null, true); // noThrottle
+        row.scrollIntoView();
+    }
+
+    function getComponentsStackDump()
+    {
+        // Starting with our stack, walk back to the user-level code
+        var frame = Components.stack;
+        var userURL = win.location.href.toString();
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("consoleInjector.getComponentsStackDump initial stack for userURL "+userURL, frame);
+
+        // Drop frames until we get into user code.
+        while (frame && FBL.isSystemURL(frame.filename) )
+            frame = frame.caller;
+
+        // Drop two more frames, the injected console function and firebugAppendConsole()
+        if (frame)
+            frame = frame.caller;
+        if (frame)
+            frame = frame.caller;
+
+        if (FBTrace.DBG_CONSOLE)
+            FBTrace.sysout("consoleInjector.getComponentsStackDump final stack for userURL "+userURL, frame);
+
+        return frame;
+    }
+
+    function getStackLink()
+    {
+        // TODO: xxxpedro console2
+        return;
+        //return FBL.getFrameSourceLink(getComponentsStackDump());
+    }
+
+    function getJSDUserStack()
+    {
+        var trace = FBL.getCurrentStackTrace(context);
+
+        var frames = trace ? trace.frames : null;
+        if (frames && (frames.length > 0) )
+        {
+            var oldest = frames.length - 1;  // 6 - 1 = 5
+            for (var i = 0; i < frames.length; i++)
+            {
+                if (frames[oldest - i].href.indexOf("chrome:") == 0) break;
+                var fn = frames[oldest - i].fn + "";
+                if (fn && (fn.indexOf("_firebugEvalEvent") != -1) ) break;  // command line
+            }
+            FBTrace.sysout("consoleInjector getJSDUserStack: "+frames.length+" oldest: "+oldest+" i: "+i+" i - oldest + 2: "+(i - oldest + 2), trace);
+            trace.frames = trace.frames.slice(2 - i);  // take the oldest frames, leave 2 behind they are injection code
+
+            return trace;
+        }
+        else
+            return "Firebug failed to get stack trace with any frames";
+    }
+};
+
+// ************************************************************************************************
+// Register console namespace
+
+FBL.registerConsole = function()
+{
+    var win = Env.browser.window;
+    Firebug.Console.injector.install(win);
+};
+
+registerConsole();
+
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+
+// ************************************************************************************************
+// Globals
+
+var commandPrefix = ">>>";
+var reOpenBracket = /[\[\(\{]/;
+var reCloseBracket = /[\]\)\}]/;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var commandHistory = [];
+var commandPointer = -1;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var isAutoCompleting = null;
+var autoCompletePrefix = null;
+var autoCompleteExpr = null;
+var autoCompleteBuffer = null;
+var autoCompletePosition = null;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var fbCommandLine = null;
+var fbLargeCommandLine = null;
+var fbLargeCommandButtons = null;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var _completion =
+{
+    window:
+    [
+        "console"
+    ],
+
+    document:
+    [
+        "getElementById",
+        "getElementsByTagName"
+    ]
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var _stack = function(command)
+{
+    Firebug.context.persistedState.commandHistory.push(command);
+    Firebug.context.persistedState.commandPointer =
+        Firebug.context.persistedState.commandHistory.length;
+};
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+// ************************************************************************************************
+// CommandLine
+
+Firebug.CommandLine = extend(Firebug.Module,
+{
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    element: null,
+    isMultiLine: false,
+    isActive: false,
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    initialize: function(doc)
+    {
+        this.clear = bind(this.clear, this);
+        this.enter = bind(this.enter, this);
+
+        this.onError = bind(this.onError, this);
+        this.onKeyDown = bind(this.onKeyDown, this);
+        this.onMultiLineKeyDown = bind(this.onMultiLineKeyDown, this);
+
+        addEvent(Firebug.browser.window, "error", this.onError);
+        addEvent(Firebug.chrome.window, "error", this.onError);
+    },
+
+    shutdown: function(doc)
+    {
+        this.deactivate();
+
+        removeEvent(Firebug.browser.window, "error", this.onError);
+        removeEvent(Firebug.chrome.window, "error", this.onError);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    activate: function(multiLine, hideToggleIcon, onRun)
+    {
+        defineCommandLineAPI();
+
+         Firebug.context.persistedState.commandHistory =
+             Firebug.context.persistedState.commandHistory || [];
+
+         Firebug.context.persistedState.commandPointer =
+             Firebug.context.persistedState.commandPointer || -1;
+
+        if (this.isActive)
+        {
+            if (this.isMultiLine == multiLine) return;
+
+            this.deactivate();
+        }
+
+        fbCommandLine = $("fbCommandLine");
+        fbLargeCommandLine = $("fbLargeCommandLine");
+        fbLargeCommandButtons = $("fbLargeCommandButtons");
+
+        if (multiLine)
+        {
+            onRun = onRun || this.enter;
+
+            this.isMultiLine = true;
+
+            this.element = fbLargeCommandLine;
+
+            addEvent(this.element, "keydown", this.onMultiLineKeyDown);
+
+            addEvent($("fbSmallCommandLineIcon"), "click", Firebug.chrome.hideLargeCommandLine);
+
+            this.runButton = new Button({
+                element: $("fbCommand_btRun"),
+                owner: Firebug.CommandLine,
+                onClick: onRun
+            });
+
+            this.runButton.initialize();
+
+            this.clearButton = new Button({
+                element: $("fbCommand_btClear"),
+                owner: Firebug.CommandLine,
+                onClick: this.clear
+            });
+
+            this.clearButton.initialize();
+        }
+        else
+        {
+            this.isMultiLine = false;
+            this.element = fbCommandLine;
+
+            if (!fbCommandLine)
+                return;
+
+            addEvent(this.element, "keydown", this.onKeyDown);
+        }
+
+        //Firebug.Console.log("activate", this.element);
+
+        if (isOpera)
+          fixOperaTabKey(this.element);
+
+        if(this.lastValue)
+            this.element.value = this.lastValue;
+
+        this.isActive = true;
+    },
+
+    deactivate: function()
+    {
+        if (!this.isActive) return;
+
+        //Firebug.Console.log("deactivate", this.element);
+
+        this.isActive = false;
+
+        this.lastValue = this.element.value;
+
+        if (this.isMultiLine)
+        {
+            removeEvent(this.element, "keydown", this.onMultiLineKeyDown);
+
+            removeEvent($("fbSmallCommandLineIcon"), "click", Firebug.chrome.hideLargeCommandLine);
+
+            this.runButton.destroy();
+            this.clearButton.destroy();
+        }
+        else
+        {
+            removeEvent(this.element, "keydown", this.onKeyDown);
+        }
+
+        this.element = null;
+        delete this.element;
+
+        fbCommandLine = null;
+        fbLargeCommandLine = null;
+        fbLargeCommandButtons = null;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    focus: function()
+    {
+        this.element.focus();
+    },
+
+    blur: function()
+    {
+        this.element.blur();
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    clear: function()
+    {
+        this.element.value = "";
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    evaluate: function(expr)
+    {
+        // TODO: need to register the API in console.firebug.commandLineAPI
+        var api = "Firebug.CommandLine.API";
+
+        var result = Firebug.context.evaluate(expr, "window", api, Firebug.Console.error);
+
+        return result;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    enter: function()
+    {
+        var command = this.element.value;
+
+        if (!command) return;
+
+        _stack(command);
+
+        Firebug.Console.log(commandPrefix + " " + stripNewLines(command),
+                Firebug.browser, "command", FirebugReps.Text);
+
+        var result = this.evaluate(command);
+
+        Firebug.Console.log(result);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    prevCommand: function()
+    {
+        if (Firebug.context.persistedState.commandPointer > 0 &&
+            Firebug.context.persistedState.commandHistory.length > 0)
+        {
+            this.element.value = Firebug.context.persistedState.commandHistory
+                                    [--Firebug.context.persistedState.commandPointer];
+        }
+    },
+
+    nextCommand: function()
+    {
+        var element = this.element;
+
+        var limit = Firebug.context.persistedState.commandHistory.length -1;
+        var i = Firebug.context.persistedState.commandPointer;
+
+        if (i < limit)
+          element.value = Firebug.context.persistedState.commandHistory
+                              [++Firebug.context.persistedState.commandPointer];
+
+        else if (i == limit)
+        {
+            ++Firebug.context.persistedState.commandPointer;
+            element.value = "";
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    autocomplete: function(reverse)
+    {
+        var element = this.element;
+
+        var command = element.value;
+        var offset = getExpressionOffset(command);
+
+        var valBegin = offset ? command.substr(0, offset) : "";
+        var val = command.substr(offset);
+
+        var buffer, obj, objName, commandBegin, result, prefix;
+
+        // if it is the beginning of the completion
+        if(!isAutoCompleting)
+        {
+
+            // group1 - command begin
+            // group2 - base object
+            // group3 - property prefix
+            var reObj = /(.*[^_$\w\d\.])?((?:[_$\w][_$\w\d]*\.)*)([_$\w][_$\w\d]*)?$/;
+            var r = reObj.exec(val);
+
+            // parse command
+            if (r[1] || r[2] || r[3])
+            {
+                commandBegin = r[1] || "";
+                objName = r[2] || "";
+                prefix = r[3] || "";
+            }
+            else if (val == "")
+            {
+                commandBegin = objName = prefix = "";
+            } else
+                return;
+
+            isAutoCompleting = true;
+
+            // find base object
+            if(objName == "")
+                obj = window;
+
+            else
+            {
+                objName = objName.replace(/\.$/, "");
+
+                var n = objName.split(".");
+                var target = window, o;
+
+                for (var i=0, ni; ni = n[i]; i++)
+                {
+                    if (o = target[ni])
+                      target = o;
+
+                    else
+                    {
+                        target = null;
+                        break;
+                    }
+                }
+                obj = target;
+            }
+
+            // map base object
+            if(obj)
+            {
+                autoCompletePrefix = prefix;
+                autoCompleteExpr = valBegin + commandBegin + (objName ? objName + "." : "");
+                autoCompletePosition = -1;
+
+                buffer = autoCompleteBuffer = isIE ?
+                    _completion[objName || "window"] || [] : [];
+
+                for(var p in obj)
+                    buffer.push(p);
+            }
+
+        // if it is the continuation of the last completion
+        } else
+          buffer = autoCompleteBuffer;
+
+        if (buffer)
+        {
+            prefix = autoCompletePrefix;
+
+            var diff = reverse ? -1 : 1;
+
+            for(var i=autoCompletePosition+diff, l=buffer.length, bi; i>=0 && i<l; i+=diff)
+            {
+                bi = buffer[i];
+
+                if (bi.indexOf(prefix) == 0)
+                {
+                    autoCompletePosition = i;
+                    result = bi;
+                    break;
+                }
+            }
+        }
+
+        if (result)
+            element.value = autoCompleteExpr + result;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    setMultiLine: function(multiLine)
+    {
+        if (multiLine == this.isMultiLine) return;
+
+        this.activate(multiLine);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onError: function(msg, href, lineNo)
+    {
+        href = href || "";
+
+        var lastSlash = href.lastIndexOf("/");
+        var fileName = lastSlash == -1 ? href : href.substr(lastSlash+1);
+        var html = [
+            '<span class="errorMessage">', msg, '</span>',
+            '<div class="objectBox-sourceLink">', fileName, ' (line ', lineNo, ')</div>'
+          ];
+
+        // TODO: xxxpedro ajust to Console2
+        //Firebug.Console.writeRow(html, "error");
+    },
+
+    onKeyDown: function(e)
+    {
+        e = e || event;
+
+        var code = e.keyCode;
+
+        /*tab, shift, control, alt*/
+        if (code != 9 && code != 16 && code != 17 && code != 18)
+        {
+            isAutoCompleting = false;
+        }
+
+        if (code == 13 /* enter */)
+        {
+            this.enter();
+            this.clear();
+        }
+        else if (code == 27 /* ESC */)
+        {
+            setTimeout(this.clear, 0);
+        }
+        else if (code == 38 /* up */)
+        {
+            this.prevCommand();
+        }
+        else if (code == 40 /* down */)
+        {
+            this.nextCommand();
+        }
+        else if (code == 9 /* tab */)
+        {
+            this.autocomplete(e.shiftKey);
+        }
+        else
+            return;
+
+        cancelEvent(e, true);
+        return false;
+    },
+
+    onMultiLineKeyDown: function(e)
+    {
+        e = e || event;
+
+        var code = e.keyCode;
+
+        if (code == 13 /* enter */ && e.ctrlKey)
+        {
+            this.enter();
+        }
+    }
+});
+
+Firebug.registerModule(Firebug.CommandLine);
+
+
+// ************************************************************************************************
+//
+
+function getExpressionOffset(command)
+{
+    // XXXjoe This is kind of a poor-man's JavaScript parser - trying
+    // to find the start of the expression that the cursor is inside.
+    // Not 100% fool proof, but hey...
+
+    var bracketCount = 0;
+
+    var start = command.length-1;
+    for (; start >= 0; --start)
+    {
+        var c = command[start];
+        if ((c == "," || c == ";" || c == " ") && !bracketCount)
+            break;
+        if (reOpenBracket.test(c))
+        {
+            if (bracketCount)
+                --bracketCount;
+            else
+                break;
+        }
+        else if (reCloseBracket.test(c))
+            ++bracketCount;
+    }
+
+    return start + 1;
+}
+
+// ************************************************************************************************
+// CommandLine API
+
+var CommandLineAPI =
+{
+    $: function(id)
+    {
+        return Firebug.browser.document.getElementById(id);
+    },
+
+    $$: function(selector, context)
+    {
+        context = context || Firebug.browser.document;
+        return Firebug.Selector ?
+                Firebug.Selector(selector, context) :
+                Firebug.Console.error("Firebug.Selector module not loaded.");
+    },
+
+    $0: null,
+
+    $1: null,
+
+    dir: function(o)
+    {
+        Firebug.Console.log(o, Firebug.context, "dir", Firebug.DOMPanel.DirTable);
+    },
+
+    dirxml: function(o)
+    {
+        ///if (o instanceof Window)
+        if (instanceOf(o, "Window"))
+            o = o.document.documentElement;
+        ///else if (o instanceof Document)
+        else if (instanceOf(o, "Document"))
+            o = o.documentElement;
+
+        Firebug.Console.log(o, Firebug.context, "dirxml", Firebug.HTMLPanel.SoloElement);
+    }
+};
+
+// ************************************************************************************************
+
+var defineCommandLineAPI = function defineCommandLineAPI()
+{
+    Firebug.CommandLine.API = {};
+    for (var m in CommandLineAPI)
+        if (!Env.browser.window[m])
+            Firebug.CommandLine.API[m] = CommandLineAPI[m];
+
+    var stack = FirebugChrome.htmlSelectionStack;
+    if (stack)
+    {
+        Firebug.CommandLine.API.$0 = stack[0];
+        Firebug.CommandLine.API.$1 = stack[1];
+    }
+};
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Globals
+
+var ElementCache = Firebug.Lite.Cache.Element;
+var cacheID = Firebug.Lite.Cache.ID;
+
+var ignoreHTMLProps =
+{
+    // ignores the attributes injected by Sizzle, otherwise it will
+    // be visible on IE (when enumerating element.attributes)
+    sizcache: 1,
+    sizset: 1
+};
+
+if (Firebug.ignoreFirebugElements)
+    // ignores also the cache property injected by firebug
+    ignoreHTMLProps[cacheID] = 1;
+
+
+// ************************************************************************************************
+// HTML Module
+
+Firebug.HTML = extend(Firebug.Module,
+{
+    appendTreeNode: function(nodeArray, html)
+    {
+        var reTrim = /^\s+|\s+$/g;
+
+        if (!nodeArray.length) nodeArray = [nodeArray];
+
+        for (var n=0, node; node=nodeArray[n]; n++)
+        {
+            if (node.nodeType == 1)
+            {
+                if (Firebug.ignoreFirebugElements && node.firebugIgnore) continue;
+
+                var uid = ElementCache(node);
+                var child = node.childNodes;
+                var childLength = child.length;
+
+                var nodeName = node.nodeName.toLowerCase();
+
+                var nodeVisible = isVisible(node);
+
+                var hasSingleTextChild = childLength == 1 && node.firstChild.nodeType == 3 &&
+                        nodeName != "script" && nodeName != "style";
+
+                var nodeControl = !hasSingleTextChild && childLength > 0 ?
+                    ('<div class="nodeControl"></div>') : '';
+
+                // FIXME xxxpedro remove this
+                //var isIE = false;
+
+                if(isIE && nodeControl)
+                    html.push(nodeControl);
+
+                if (typeof uid != 'undefined')
+                    html.push(
+                        '<div class="objectBox-element" ',
+                        'id="', uid,
+                        '">',
+                        !isIE && nodeControl ? nodeControl: "",
+                        '<span ',
+                        cacheID,
+                        '="', uid,
+                        '"  class="nodeBox',
+                        nodeVisible ? "" : " nodeHidden",
+                        '">&lt;<span class="nodeTag">', nodeName, '</span>'
+                    );
+                else
+                    html.push(
+                        '<div class="objectBox-element"><span class="nodeBox',
+                        nodeVisible ? "" : " nodeHidden",
+                        '">&lt;<span class="nodeTag">',
+                        nodeName, '</span>'
+                    );
+
+                for (var i = 0; i < node.attributes.length; ++i)
+                {
+                    var attr = node.attributes[i];
+                    if (!attr.specified ||
+                        // Issue 4432:  Firebug Lite: HTML is mixed-up with functions
+                        // The problem here is that expando properties added to DOM elements in
+                        // IE < 9 will behave like DOM attributes and so they'll show up when
+                        // looking at element.attributes list.
+                        isIE && (browserVersion-0<9) && typeof attr.nodeValue != "string" ||
+                        Firebug.ignoreFirebugElements && ignoreHTMLProps.hasOwnProperty(attr.nodeName))
+                            continue;
+
+                    var name = attr.nodeName.toLowerCase();
+                    var value = name == "style" ? formatStyles(node.style.cssText) : attr.nodeValue;
+
+                    html.push('&nbsp;<span class="nodeName">', name,
+                        '</span>=&quot;<span class="nodeValue">', escapeHTML(value),
+                        '</span>&quot;');
+                }
+
+                /*
+                // source code nodes
+                if (nodeName == 'script' || nodeName == 'style')
+                {
+
+                    if(document.all){
+                        var src = node.innerHTML+'\n';
+
+                    }else {
+                        var src = '\n'+node.innerHTML+'\n';
+                    }
+
+                    var match = src.match(/\n/g);
+                    var num = match ? match.length : 0;
+                    var s = [], sl = 0;
+
+                    for(var c=1; c<num; c++){
+                        s[sl++] = '<div line="'+c+'">' + c + '</div>';
+                    }
+
+                    html.push('&gt;</div><div class="nodeGroup"><div class="nodeChildren"><div class="lineNo">',
+                            s.join(''),
+                            '</div><pre class="nodeCode">',
+                            escapeHTML(src),
+                            '</pre>',
+                            '</div><div class="objectBox-element">&lt;/<span class="nodeTag">',
+                            nodeName,
+                            '</span>&gt;</div>',
+                            '</div>'
+                        );
+
+
+                }/**/
+
+                // Just a single text node child
+                if (hasSingleTextChild)
+                {
+                    var value = child[0].nodeValue.replace(reTrim, '');
+                    if(value)
+                    {
+                        html.push(
+                                '&gt;<span class="nodeText">',
+                                escapeHTML(value),
+                                '</span>&lt;/<span class="nodeTag">',
+                                nodeName,
+                                '</span>&gt;</span></div>'
+                            );
+                    }
+                    else
+                      html.push('/&gt;</span></div>'); // blank text, print as childless node
+
+                }
+                else if (childLength > 0)
+                {
+                    html.push('&gt;</span></div>');
+                }
+                else
+                    html.push('/&gt;</span></div>');
+
+            }
+            else if (node.nodeType == 3)
+            {
+                if ( node.parentNode && ( node.parentNode.nodeName.toLowerCase() == "script" ||
+                     node.parentNode.nodeName.toLowerCase() == "style" ) )
+                {
+                    var value = node.nodeValue.replace(reTrim, '');
+
+                    if(isIE){
+                        var src = value+'\n';
+
+                    }else {
+                        var src = '\n'+value+'\n';
+                    }
+
+                    var match = src.match(/\n/g);
+                    var num = match ? match.length : 0;
+                    var s = [], sl = 0;
+
+                    for(var c=1; c<num; c++){
+                        s[sl++] = '<div line="'+c+'">' + c + '</div>';
+                    }
+
+                    html.push('<div class="lineNo">',
+                            s.join(''),
+                            '</div><pre class="sourceCode">',
+                            escapeHTML(src),
+                            '</pre>'
+                        );
+
+                }
+                else
+                {
+                    var value = node.nodeValue.replace(reTrim, '');
+                    if (value)
+                        html.push('<div class="nodeText">', escapeHTML(value),'</div>');
+                }
+            }
+        }
+    },
+
+    appendTreeChildren: function(treeNode)
+    {
+        var doc = Firebug.chrome.document;
+        var uid = treeNode.id;
+        var parentNode = ElementCache.get(uid);
+
+        if (parentNode.childNodes.length == 0) return;
+
+        var treeNext = treeNode.nextSibling;
+        var treeParent = treeNode.parentNode;
+
+        // FIXME xxxpedro remove this
+        //var isIE = false;
+        var control = isIE ? treeNode.previousSibling : treeNode.firstChild;
+        control.className = 'nodeControl nodeMaximized';
+
+        var html = [];
+        var children = doc.createElement("div");
+        children.className = "nodeChildren";
+        this.appendTreeNode(parentNode.childNodes, html);
+        children.innerHTML = html.join("");
+
+        treeParent.insertBefore(children, treeNext);
+
+        var closeElement = doc.createElement("div");
+        closeElement.className = "objectBox-element";
+        closeElement.innerHTML = '&lt;/<span class="nodeTag">' +
+            parentNode.nodeName.toLowerCase() + '&gt;</span>';
+
+        treeParent.insertBefore(closeElement, treeNext);
+
+    },
+
+    removeTreeChildren: function(treeNode)
+    {
+        var children = treeNode.nextSibling;
+        var closeTag = children.nextSibling;
+
+        // FIXME xxxpedro remove this
+        //var isIE = false;
+        var control = isIE ? treeNode.previousSibling : treeNode.firstChild;
+        control.className = 'nodeControl';
+
+        children.parentNode.removeChild(children);
+        closeTag.parentNode.removeChild(closeTag);
+    },
+
+    isTreeNodeVisible: function(id)
+    {
+        return $(id);
+    },
+
+    select: function(el)
+    {
+        var id = el && ElementCache(el);
+        if (id)
+            this.selectTreeNode(id);
+    },
+
+    selectTreeNode: function(id)
+    {
+        id = ""+id;
+        var node, stack = [];
+        while(id && !this.isTreeNodeVisible(id))
+        {
+            stack.push(id);
+
+            var node = ElementCache.get(id).parentNode;
+
+            if (node)
+                id = ElementCache(node);
+            else
+                break;
+        }
+
+        stack.push(id);
+
+        while(stack.length > 0)
+        {
+            id = stack.pop();
+            node = $(id);
+
+            if (stack.length > 0 && ElementCache.get(id).childNodes.length > 0)
+              this.appendTreeChildren(node);
+        }
+
+        selectElement(node);
+
+        // TODO: xxxpedro
+        if (fbPanel1)
+            fbPanel1.scrollTop = Math.round(node.offsetTop - fbPanel1.clientHeight/2);
+    }
+
+});
+
+Firebug.registerModule(Firebug.HTML);
+
+// ************************************************************************************************
+// HTML Panel
+
+function HTMLPanel(){};
+
+HTMLPanel.prototype = extend(Firebug.Panel,
+{
+    name: "HTML",
+    title: "HTML",
+
+    options: {
+        hasSidePanel: true,
+        //hasToolButtons: true,
+        isPreRendered: !Firebug.flexChromeEnabled /* FIXME xxxpedro chromenew */,
+        innerHTMLSync: true
+    },
+
+    create: function(){
+        Firebug.Panel.create.apply(this, arguments);
+
+        this.panelNode.style.padding = "4px 3px 1px 15px";
+        this.panelNode.style.minWidth = "500px";
+
+        if (Env.Options.enablePersistent || Firebug.chrome.type != "popup")
+            this.createUI();
+
+        if(this.sidePanelBar && !this.sidePanelBar.selectedPanel)
+        {
+            this.sidePanelBar.selectPanel("css");
+        }
+    },
+
+    destroy: function()
+    {
+        selectedElement = null;
+        fbPanel1 = null;
+
+        selectedSidePanelTS = null;
+        selectedSidePanelTimer = null;
+
+        Firebug.Panel.destroy.apply(this, arguments);
+    },
+
+    createUI: function()
+    {
+        var rootNode = Firebug.browser.document.documentElement;
+        var html = [];
+        Firebug.HTML.appendTreeNode(rootNode, html);
+
+        this.panelNode.innerHTML = html.join("");
+    },
+
+    initialize: function()
+    {
+        Firebug.Panel.initialize.apply(this, arguments);
+        addEvent(this.panelNode, 'click', Firebug.HTML.onTreeClick);
+
+        fbPanel1 = $("fbPanel1");
+
+        if(!selectedElement)
+        {
+            Firebug.context.persistedState.selectedHTMLElementId =
+                Firebug.context.persistedState.selectedHTMLElementId &&
+                ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId) ?
+                Firebug.context.persistedState.selectedHTMLElementId :
+                ElementCache(Firebug.browser.document.body);
+
+            Firebug.HTML.selectTreeNode(Firebug.context.persistedState.selectedHTMLElementId);
+        }
+
+        // TODO: xxxpedro
+        addEvent(fbPanel1, 'mousemove', Firebug.HTML.onListMouseMove);
+        addEvent($("fbContent"), 'mouseout', Firebug.HTML.onListMouseMove);
+        addEvent(Firebug.chrome.node, 'mouseout', Firebug.HTML.onListMouseMove);
+    },
+
+    shutdown: function()
+    {
+        // TODO: xxxpedro
+        removeEvent(fbPanel1, 'mousemove', Firebug.HTML.onListMouseMove);
+        removeEvent($("fbContent"), 'mouseout', Firebug.HTML.onListMouseMove);
+        removeEvent(Firebug.chrome.node, 'mouseout', Firebug.HTML.onListMouseMove);
+
+        removeEvent(this.panelNode, 'click', Firebug.HTML.onTreeClick);
+
+        fbPanel1 = null;
+
+        Firebug.Panel.shutdown.apply(this, arguments);
+    },
+
+    reattach: function()
+    {
+        // TODO: panel reattach
+        if(Firebug.context.persistedState.selectedHTMLElementId)
+            Firebug.HTML.selectTreeNode(Firebug.context.persistedState.selectedHTMLElementId);
+    },
+
+    updateSelection: function(object)
+    {
+        var id = ElementCache(object);
+
+        if (id)
+        {
+            Firebug.HTML.selectTreeNode(id);
+        }
+    }
+});
+
+Firebug.registerPanel(HTMLPanel);
+
+// ************************************************************************************************
+
+var formatStyles = function(styles)
+{
+    return isIE ?
+        // IE return CSS property names in upper case, so we need to convert them
+        styles.replace(/([^\s]+)\s*:/g, function(m,g){return g.toLowerCase()+":";}) :
+        // other browsers are just fine
+        styles;
+};
+
+// ************************************************************************************************
+
+var selectedElement = null;
+var fbPanel1 = null;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+var selectedSidePanelTS, selectedSidePanelTimer;
+
+var selectElement= function selectElement(e)
+{
+    if (e != selectedElement)
+    {
+        if (selectedElement)
+            selectedElement.className = "objectBox-element";
+
+        e.className = e.className + " selectedElement";
+
+        if (FBL.isFirefox)
+            e.style.MozBorderRadius = "2px";
+
+        else if (FBL.isSafari)
+            e.style.WebkitBorderRadius = "2px";
+
+        e.style.borderRadius = "2px";
+
+        selectedElement = e;
+
+        Firebug.context.persistedState.selectedHTMLElementId = e.id;
+
+        var target = ElementCache.get(e.id);
+        var sidePanelBar = Firebug.chrome.getPanel("HTML").sidePanelBar;
+        var selectedSidePanel = sidePanelBar ? sidePanelBar.selectedPanel : null;
+
+        var stack = FirebugChrome.htmlSelectionStack;
+
+        stack.unshift(target);
+
+        if (stack.length > 2)
+            stack.pop();
+
+        var lazySelect = function()
+        {
+            selectedSidePanelTS = new Date().getTime();
+
+            if (selectedSidePanel)
+                selectedSidePanel.select(target, true);
+        };
+
+        if (selectedSidePanelTimer)
+        {
+            clearTimeout(selectedSidePanelTimer);
+            selectedSidePanelTimer = null;
+        }
+
+        if (new Date().getTime() - selectedSidePanelTS > 100)
+            setTimeout(lazySelect, 0);
+        else
+            selectedSidePanelTimer = setTimeout(lazySelect, 150);
+    }
+};
+
+
+// ************************************************************************************************
+// ***  TODO:  REFACTOR  **************************************************************************
+// ************************************************************************************************
+Firebug.HTML.onTreeClick = function (e)
+{
+    e = e || event;
+    var targ;
+
+    if (e.target) targ = e.target;
+    else if (e.srcElement) targ = e.srcElement;
+    if (targ.nodeType == 3) // defeat Safari bug
+        targ = targ.parentNode;
+
+
+    if (targ.className.indexOf('nodeControl') != -1 || targ.className == 'nodeTag')
+    {
+        // FIXME xxxpedro remove this
+        //var isIE = false;
+
+        if(targ.className == 'nodeTag')
+        {
+            var control = isIE ? (targ.parentNode.previousSibling || targ) :
+                          (targ.parentNode.previousSibling || targ);
+
+            selectElement(targ.parentNode.parentNode);
+
+            if (control.className.indexOf('nodeControl') == -1)
+                return;
+
+        } else
+            control = targ;
+
+        FBL.cancelEvent(e);
+
+        var treeNode = isIE ? control.nextSibling : control.parentNode;
+
+        //FBL.Firebug.Console.log(treeNode);
+
+        if (control.className.indexOf(' nodeMaximized') != -1) {
+            FBL.Firebug.HTML.removeTreeChildren(treeNode);
+        } else {
+            FBL.Firebug.HTML.appendTreeChildren(treeNode);
+        }
+    }
+    else if (targ.className == 'nodeValue' || targ.className == 'nodeName')
+    {
+        /*
+        var input = FBL.Firebug.chrome.document.getElementById('treeInput');
+
+        input.style.display = "block";
+        input.style.left = targ.offsetLeft + 'px';
+        input.style.top = FBL.topHeight + targ.offsetTop - FBL.fbPanel1.scrollTop + 'px';
+        input.style.width = targ.offsetWidth + 6 + 'px';
+        input.value = targ.textContent || targ.innerText;
+        input.focus();
+        /**/
+    }
+};
+
+function onListMouseOut(e)
+{
+    e = e || event || window;
+    var targ;
+
+    if (e.target) targ = e.target;
+    else if (e.srcElement) targ = e.srcElement;
+    if (targ.nodeType == 3) // defeat Safari bug
+      targ = targ.parentNode;
+
+      if (hasClass(targ, "fbPanel")) {
+          FBL.Firebug.Inspector.hideBoxModel();
+          hoverElement = null;
+      }
+};
+
+var hoverElement = null;
+var hoverElementTS = 0;
+
+Firebug.HTML.onListMouseMove = function onListMouseMove(e)
+{
+    try
+    {
+        e = e || event || window;
+        var targ;
+
+        if (e.target) targ = e.target;
+        else if (e.srcElement) targ = e.srcElement;
+        if (targ.nodeType == 3) // defeat Safari bug
+            targ = targ.parentNode;
+
+        var found = false;
+        while (targ && !found) {
+            if (!/\snodeBox\s|\sobjectBox-selector\s/.test(" " + targ.className + " "))
+                targ = targ.parentNode;
+            else
+                found = true;
+        }
+
+        if (!targ)
+        {
+            FBL.Firebug.Inspector.hideBoxModel();
+            hoverElement = null;
+            return;
+        }
+
+        /*
+        if (typeof targ.attributes[cacheID] == 'undefined') return;
+
+        var uid = targ.attributes[cacheID];
+        if (!uid) return;
+        /**/
+
+        if (typeof targ.attributes[cacheID] == 'undefined') return;
+
+        var uid = targ.attributes[cacheID];
+        if (!uid) return;
+
+        var el = ElementCache.get(uid.value);
+
+        var nodeName = el.nodeName.toLowerCase();
+
+        if (FBL.isIE && " meta title script link ".indexOf(" "+nodeName+" ") != -1)
+            return;
+
+        if (!/\snodeBox\s|\sobjectBox-selector\s/.test(" " + targ.className + " ")) return;
+
+        if (el.id == "FirebugUI" || " html head body br script link iframe ".indexOf(" "+nodeName+" ") != -1) {
+            FBL.Firebug.Inspector.hideBoxModel();
+            hoverElement = null;
+            return;
+        }
+
+        if ((new Date().getTime() - hoverElementTS > 40) && hoverElement != el) {
+            hoverElementTS = new Date().getTime();
+            hoverElement = el;
+            FBL.Firebug.Inspector.drawBoxModel(el);
+        }
+    }
+    catch(E)
+    {
+    }
+};
+
+
+// ************************************************************************************************
+
+Firebug.Reps = {
+
+    appendText: function(object, html)
+    {
+        html.push(escapeHTML(objectToString(object)));
+    },
+
+    appendNull: function(object, html)
+    {
+        html.push('<span class="objectBox-null">', escapeHTML(objectToString(object)), '</span>');
+    },
+
+    appendString: function(object, html)
+    {
+        html.push('<span class="objectBox-string">&quot;', escapeHTML(objectToString(object)),
+            '&quot;</span>');
+    },
+
+    appendInteger: function(object, html)
+    {
+        html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
+    },
+
+    appendFloat: function(object, html)
+    {
+        html.push('<span class="objectBox-number">', escapeHTML(objectToString(object)), '</span>');
+    },
+
+    appendFunction: function(object, html)
+    {
+        var reName = /function ?(.*?)\(/;
+        var m = reName.exec(objectToString(object));
+        var name = m && m[1] ? m[1] : "function";
+        html.push('<span class="objectBox-function">', escapeHTML(name), '()</span>');
+    },
+
+    appendObject: function(object, html)
+    {
+        /*
+        var rep = Firebug.getRep(object);
+        var outputs = [];
+
+        rep.tag.tag.compile();
+
+        var str = rep.tag.renderHTML({object: object}, outputs);
+        html.push(str);
+        /**/
+
+        try
+        {
+            if (object == undefined)
+                this.appendNull("undefined", html);
+            else if (object == null)
+                this.appendNull("null", html);
+            else if (typeof object == "string")
+                this.appendString(object, html);
+            else if (typeof object == "number")
+                this.appendInteger(object, html);
+            else if (typeof object == "boolean")
+                this.appendInteger(object, html);
+            else if (typeof object == "function")
+                this.appendFunction(object, html);
+            else if (object.nodeType == 1)
+                this.appendSelector(object, html);
+            else if (typeof object == "object")
+            {
+                if (typeof object.length != "undefined")
+                    this.appendArray(object, html);
+                else
+                    this.appendObjectFormatted(object, html);
+            }
+            else
+                this.appendText(object, html);
+        }
+        catch (exc)
+        {
+        }
+        /**/
+    },
+
+    appendObjectFormatted: function(object, html)
+    {
+        var text = objectToString(object);
+        var reObject = /\[object (.*?)\]/;
+
+        var m = reObject.exec(text);
+        html.push('<span class="objectBox-object">', m ? m[1] : text, '</span>');
+    },
+
+    appendSelector: function(object, html)
+    {
+        var uid = ElementCache(object);
+        var uidString = uid ? [cacheID, '="', uid, '"'].join("") : "";
+
+        html.push('<span class="objectBox-selector"', uidString, '>');
+
+        html.push('<span class="selectorTag">', escapeHTML(object.nodeName.toLowerCase()), '</span>');
+        if (object.id)
+            html.push('<span class="selectorId">#', escapeHTML(object.id), '</span>');
+        if (object.className)
+            html.push('<span class="selectorClass">.', escapeHTML(object.className), '</span>');
+
+        html.push('</span>');
+    },
+
+    appendNode: function(node, html)
+    {
+        if (node.nodeType == 1)
+        {
+            var uid = ElementCache(node);
+            var uidString = uid ? [cacheID, '="', uid, '"'].join("") : "";
+
+            html.push(
+                '<div class="objectBox-element"', uidString, '">',
+                '<span ', cacheID, '="', uid, '" class="nodeBox">',
+                '&lt;<span class="nodeTag">', node.nodeName.toLowerCase(), '</span>');
+
+            for (var i = 0; i < node.attributes.length; ++i)
+            {
+                var attr = node.attributes[i];
+                if (!attr.specified || attr.nodeName == cacheID)
+                    continue;
+
+                var name = attr.nodeName.toLowerCase();
+                var value = name == "style" ? node.style.cssText : attr.nodeValue;
+
+                html.push('&nbsp;<span class="nodeName">', name,
+                    '</span>=&quot;<span class="nodeValue">', escapeHTML(value),
+                    '</span>&quot;');
+            }
+
+            if (node.firstChild)
+            {
+                html.push('&gt;</div><div class="nodeChildren">');
+
+                for (var child = node.firstChild; child; child = child.nextSibling)
+                    this.appendNode(child, html);
+
+                html.push('</div><div class="objectBox-element">&lt;/<span class="nodeTag">',
+                    node.nodeName.toLowerCase(), '&gt;</span></span></div>');
+            }
+            else
+                html.push('/&gt;</span></div>');
+        }
+        else if (node.nodeType == 3)
+        {
+            var value = trim(node.nodeValue);
+            if (value)
+                html.push('<div class="nodeText">', escapeHTML(value),'</div>');
+        }
+    },
+
+    appendArray: function(object, html)
+    {
+        html.push('<span class="objectBox-array"><b>[</b> ');
+
+        for (var i = 0, l = object.length, obj; i < l; ++i)
+        {
+            this.appendObject(object[i], html);
+
+            if (i < l-1)
+            html.push(', ');
+        }
+
+        html.push(' <b>]</b></span>');
+    }
+
+};
+
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+/*
+
+Hack:
+Firebug.chrome.currentPanel = Firebug.chrome.selectedPanel;
+Firebug.showInfoTips = true;
+Firebug.InfoTip.initializeBrowser(Firebug.chrome);
+
+/**/
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// Constants
+
+var maxWidth = 100, maxHeight = 80;
+var infoTipMargin = 10;
+var infoTipWindowPadding = 25;
+
+// ************************************************************************************************
+
+Firebug.InfoTip = extend(Firebug.Module,
+{
+    dispatchName: "infoTip",
+    tags: domplate(
+    {
+        infoTipTag: DIV({"class": "infoTip"}),
+
+        colorTag:
+            DIV({style: "background: $rgbValue; width: 100px; height: 40px"}, "&nbsp;"),
+
+        imgTag:
+            DIV({"class": "infoTipImageBox infoTipLoading"},
+                IMG({"class": "infoTipImage", src: "$urlValue", repeat: "$repeat",
+                    onload: "$onLoadImage"}),
+                IMG({"class": "infoTipBgImage", collapsed: true, src: "blank.gif"}),
+                DIV({"class": "infoTipCaption"})
+            ),
+
+        onLoadImage: function(event)
+        {
+            var img = event.currentTarget || event.srcElement;
+            ///var bgImg = img.nextSibling;
+            ///if (!bgImg)
+            ///    return; // Sometimes gets called after element is dead
+
+            ///var caption = bgImg.nextSibling;
+            var innerBox = img.parentNode;
+
+            /// TODO: xxxpedro infoTip hack
+            var caption = getElementByClass(innerBox, "infoTipCaption");
+            var bgImg = getElementByClass(innerBox, "infoTipBgImage");
+            if (!bgImg)
+                return; // Sometimes gets called after element is dead
+
+            // TODO: xxxpedro infoTip IE and timing issue
+            // TODO: use offline document to avoid flickering
+            if (isIE)
+                removeClass(innerBox, "infoTipLoading");
+
+            var updateInfoTip = function(){
+
+            var w = img.naturalWidth || img.width || 10,
+                h = img.naturalHeight || img.height || 10;
+
+            var repeat = img.getAttribute("repeat");
+
+            if (repeat == "repeat-x" || (w == 1 && h > 1))
+            {
+                collapse(img, true);
+                collapse(bgImg, false);
+                bgImg.style.background = "url(" + img.src + ") repeat-x";
+                bgImg.style.width = maxWidth + "px";
+                if (h > maxHeight)
+                    bgImg.style.height = maxHeight + "px";
+                else
+                    bgImg.style.height = h + "px";
+            }
+            else if (repeat == "repeat-y" || (h == 1 && w > 1))
+            {
+                collapse(img, true);
+                collapse(bgImg, false);
+                bgImg.style.background = "url(" + img.src + ") repeat-y";
+                bgImg.style.height = maxHeight + "px";
+                if (w > maxWidth)
+                    bgImg.style.width = maxWidth + "px";
+                else
+                    bgImg.style.width = w + "px";
+            }
+            else if (repeat == "repeat" || (w == 1 && h == 1))
+            {
+                collapse(img, true);
+                collapse(bgImg, false);
+                bgImg.style.background = "url(" + img.src + ") repeat";
+                bgImg.style.width = maxWidth + "px";
+                bgImg.style.height = maxHeight + "px";
+            }
+            else
+            {
+                if (w > maxWidth || h > maxHeight)
+                {
+                    if (w > h)
+                    {
+                        img.style.width = maxWidth + "px";
+                        img.style.height = Math.round((h / w) * maxWidth) + "px";
+                    }
+                    else
+                    {
+                        img.style.width = Math.round((w / h) * maxHeight) + "px";
+                        img.style.height = maxHeight + "px";
+                    }
+                }
+            }
+
+            //caption.innerHTML = $STRF("Dimensions", [w, h]);
+            caption.innerHTML = $STRF(w + " x " + h);
+
+
+            };
+
+            if (isIE)
+                setTimeout(updateInfoTip, 0);
+            else
+            {
+                updateInfoTip();
+                removeClass(innerBox, "infoTipLoading");
+            }
+
+            ///
+        }
+
+        /*
+        /// onLoadImage original
+        onLoadImage: function(event)
+        {
+            var img = event.currentTarget;
+            var bgImg = img.nextSibling;
+            if (!bgImg)
+                return; // Sometimes gets called after element is dead
+
+            var caption = bgImg.nextSibling;
+            var innerBox = img.parentNode;
+
+            var w = img.naturalWidth, h = img.naturalHeight;
+            var repeat = img.getAttribute("repeat");
+
+            if (repeat == "repeat-x" || (w == 1 && h > 1))
+            {
+                collapse(img, true);
+                collapse(bgImg, false);
+                bgImg.style.background = "url(" + img.src + ") repeat-x";
+                bgImg.style.width = maxWidth + "px";
+                if (h > maxHeight)
+                    bgImg.style.height = maxHeight + "px";
+                else
+                    bgImg.style.height = h + "px";
+            }
+            else if (repeat == "repeat-y" || (h == 1 && w > 1))
+            {
+                collapse(img, true);
+                collapse(bgImg, false);
+                bgImg.style.background = "url(" + img.src + ") repeat-y";
+                bgImg.style.height = maxHeight + "px";
+                if (w > maxWidth)
+                    bgImg.style.width = maxWidth + "px";
+                else
+                    bgImg.style.width = w + "px";
+            }
+            else if (repeat == "repeat" || (w == 1 && h == 1))
+            {
+                collapse(img, true);
+                collapse(bgImg, false);
+                bgImg.style.background = "url(" + img.src + ") repeat";
+                bgImg.style.width = maxWidth + "px";
+                bgImg.style.height = maxHeight + "px";
+            }
+            else
+            {
+                if (w > maxWidth || h > maxHeight)
+                {
+                    if (w > h)
+                    {
+                        img.style.width = maxWidth + "px";
+                        img.style.height = Math.round((h / w) * maxWidth) + "px";
+                    }
+                    else
+                    {
+                        img.style.width = Math.round((w / h) * maxHeight) + "px";
+                        img.style.height = maxHeight + "px";
+                    }
+                }
+            }
+
+            caption.innerHTML = $STRF("Dimensions", [w, h]);
+
+            removeClass(innerBox, "infoTipLoading");
+        }
+        /**/
+
+    }),
+
+    initializeBrowser: function(browser)
+    {
+        browser.onInfoTipMouseOut = bind(this.onMouseOut, this, browser);
+        browser.onInfoTipMouseMove = bind(this.onMouseMove, this, browser);
+
+        ///var doc = browser.contentDocument;
+        var doc = browser.document;
+        if (!doc)
+            return;
+
+        ///doc.addEventListener("mouseover", browser.onInfoTipMouseMove, true);
+        ///doc.addEventListener("mouseout", browser.onInfoTipMouseOut, true);
+        ///doc.addEventListener("mousemove", browser.onInfoTipMouseMove, true);
+        addEvent(doc, "mouseover", browser.onInfoTipMouseMove);
+        addEvent(doc, "mouseout", browser.onInfoTipMouseOut);
+        addEvent(doc, "mousemove", browser.onInfoTipMouseMove);
+
+        return browser.infoTip = this.tags.infoTipTag.append({}, getBody(doc));
+    },
+
+    uninitializeBrowser: function(browser)
+    {
+        if (browser.infoTip)
+        {
+            ///var doc = browser.contentDocument;
+            var doc = browser.document;
+            ///doc.removeEventListener("mouseover", browser.onInfoTipMouseMove, true);
+            ///doc.removeEventListener("mouseout", browser.onInfoTipMouseOut, true);
+            ///doc.removeEventListener("mousemove", browser.onInfoTipMouseMove, true);
+            removeEvent(doc, "mouseover", browser.onInfoTipMouseMove);
+            removeEvent(doc, "mouseout", browser.onInfoTipMouseOut);
+            removeEvent(doc, "mousemove", browser.onInfoTipMouseMove);
+
+            browser.infoTip.parentNode.removeChild(browser.infoTip);
+            delete browser.infoTip;
+            delete browser.onInfoTipMouseMove;
+        }
+    },
+
+    showInfoTip: function(infoTip, panel, target, x, y, rangeParent, rangeOffset)
+    {
+        if (!Firebug.showInfoTips)
+            return;
+
+        var scrollParent = getOverflowParent(target);
+        var scrollX = x + (scrollParent ? scrollParent.scrollLeft : 0);
+
+        if (panel.showInfoTip(infoTip, target, scrollX, y, rangeParent, rangeOffset))
+        {
+            var htmlElt = infoTip.ownerDocument.documentElement;
+            var panelWidth = htmlElt.clientWidth;
+            var panelHeight = htmlElt.clientHeight;
+
+            if (x+infoTip.offsetWidth+infoTipMargin > panelWidth)
+            {
+                infoTip.style.left = Math.max(0, panelWidth-(infoTip.offsetWidth+infoTipMargin)) + "px";
+                infoTip.style.right = "auto";
+            }
+            else
+            {
+                infoTip.style.left = (x+infoTipMargin) + "px";
+                infoTip.style.right = "auto";
+            }
+
+            if (y+infoTip.offsetHeight+infoTipMargin > panelHeight)
+            {
+                infoTip.style.top = Math.max(0, panelHeight-(infoTip.offsetHeight+infoTipMargin)) + "px";
+                infoTip.style.bottom = "auto";
+            }
+            else
+            {
+                infoTip.style.top = (y+infoTipMargin) + "px";
+                infoTip.style.bottom = "auto";
+            }
+
+            if (FBTrace.DBG_INFOTIP)
+                FBTrace.sysout("infotip.showInfoTip; top: " + infoTip.style.top +
+                    ", left: " + infoTip.style.left + ", bottom: " + infoTip.style.bottom +
+                    ", right:" + infoTip.style.right + ", offsetHeight: " + infoTip.offsetHeight +
+                    ", offsetWidth: " + infoTip.offsetWidth +
+                    ", x: " + x + ", panelWidth: " + panelWidth +
+                    ", y: " + y + ", panelHeight: " + panelHeight);
+
+            infoTip.setAttribute("active", "true");
+        }
+        else
+            this.hideInfoTip(infoTip);
+    },
+
+    hideInfoTip: function(infoTip)
+    {
+        if (infoTip)
+            infoTip.removeAttribute("active");
+    },
+
+    onMouseOut: function(event, browser)
+    {
+        if (!event.relatedTarget)
+            this.hideInfoTip(browser.infoTip);
+    },
+
+    onMouseMove: function(event, browser)
+    {
+        // Ignore if the mouse is moving over the existing info tip.
+        if (getAncestorByClass(event.target, "infoTip"))
+            return;
+
+        if (browser.currentPanel)
+        {
+            var x = event.clientX, y = event.clientY, target = event.target || event.srcElement;
+            this.showInfoTip(browser.infoTip, browser.currentPanel, target, x, y, event.rangeParent, event.rangeOffset);
+        }
+        else
+            this.hideInfoTip(browser.infoTip);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    populateColorInfoTip: function(infoTip, color)
+    {
+        this.tags.colorTag.replace({rgbValue: color}, infoTip);
+        return true;
+    },
+
+    populateImageInfoTip: function(infoTip, url, repeat)
+    {
+        if (!repeat)
+            repeat = "no-repeat";
+
+        this.tags.imgTag.replace({urlValue: url, repeat: repeat}, infoTip);
+
+        return true;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Module
+
+    disable: function()
+    {
+        // XXXjoe For each browser, call uninitializeBrowser
+    },
+
+    showPanel: function(browser, panel)
+    {
+        if (panel)
+        {
+            var infoTip = panel.panelBrowser.infoTip;
+            if (!infoTip)
+                infoTip = this.initializeBrowser(panel.panelBrowser);
+            this.hideInfoTip(infoTip);
+        }
+
+    },
+
+    showSidePanel: function(browser, panel)
+    {
+        this.showPanel(browser, panel);
+    }
+});
+
+// ************************************************************************************************
+
+Firebug.registerModule(Firebug.InfoTip);
+
+// ************************************************************************************************
+
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+var CssParser = null;
+
+// ************************************************************************************************
+
+// Simple CSS stylesheet parser from:
+// https://github.com/sergeche/webkit-css
+
+/**
+ * Simple CSS stylesheet parser that remembers rule's lines in file
+ * @author Sergey Chikuyonok (serge.che@gmail.com)
+ * @link http://chikuyonok.ru
+ */
+CssParser = (function(){
+    /**
+     * Returns rule object
+     * @param {Number} start Character index where CSS rule definition starts
+     * @param {Number} body_start Character index where CSS rule's body starts
+     * @param {Number} end Character index where CSS rule definition ends
+     */
+    function rule(start, body_start, end) {
+        return {
+            start: start || 0,
+            body_start: body_start || 0,
+            end: end || 0,
+            line: -1,
+            selector: null,
+            parent: null,
+
+            /** @type {rule[]} */
+            children: [],
+
+            addChild: function(start, body_start, end) {
+                var r = rule(start, body_start, end);
+                r.parent = this;
+                this.children.push(r);
+                return r;
+            },
+            /**
+             * Returns last child element
+             * @return {rule}
+             */
+            lastChild: function() {
+                return this.children[this.children.length - 1];
+            }
+        };
+    }
+
+    /**
+     * Replaces all occurances of substring defined by regexp
+     * @param {String} str
+     * @return {RegExp} re
+     * @return {String}
+     */
+    function removeAll(str, re) {
+        var m;
+        while (m = str.match(re)) {
+            str = str.substring(m[0].length);
+        }
+
+        return str;
+    }
+
+    /**
+     * Trims whitespace from the beginning and the end of string
+     * @param {String} str
+     * @return {String}
+     */
+    function trim(str) {
+        return str.replace(/^\s+|\s+$/g, '');
+    }
+
+    /**
+     * Normalizes CSS rules selector
+     * @param {String} selector
+     */
+    function normalizeSelector(selector) {
+        // remove newlines
+        selector = selector.replace(/[\n\r]/g, ' ');
+
+        selector = trim(selector);
+
+        // remove spaces after commas
+        selector = selector.replace(/\s*,\s*/g, ',');
+
+        return selector;
+    }
+
+    /**
+     * Preprocesses parsed rules: adjusts char indexes, skipping whitespace and
+     * newlines, saves rule selector, removes comments, etc.
+     * @param {String} text CSS stylesheet
+     * @param {rule} rule_node CSS rule node
+     * @return {rule[]}
+     */
+    function preprocessRules(text, rule_node) {
+        for (var i = 0, il = rule_node.children.length; i < il; i++) {
+            var r = rule_node.children[i],
+                rule_start = text.substring(r.start, r.body_start),
+                cur_len = rule_start.length;
+
+            // remove newlines for better regexp matching
+            rule_start = rule_start.replace(/[\n\r]/g, ' ');
+
+            // remove @import rules
+//            rule_start = removeAll(rule_start, /^\s*@import\s*url\((['"])?.+?\1?\)\;?/g);
+
+            // remove comments
+            rule_start = removeAll(rule_start, /^\s*\/\*.*?\*\/[\s\t]*/);
+
+            // remove whitespace
+            rule_start = rule_start.replace(/^[\s\t]+/, '');
+
+            r.start += (cur_len - rule_start.length);
+            r.selector = normalizeSelector(rule_start);
+        }
+
+        return rule_node;
+    }
+
+    /**
+     * Saves all lise starting indexes for faster search
+     * @param {String} text CSS stylesheet
+     * @return {Number[]}
+     */
+    function saveLineIndexes(text) {
+        var result = [0],
+            i = 0,
+            il = text.length,
+            ch, ch2;
+
+        while (i < il) {
+            ch = text.charAt(i);
+
+            if (ch == '\n' || ch == '\r') {
+                if (ch == '\r' && i < il - 1 && text.charAt(i + 1) == '\n') {
+                    // windows line ending: CRLF. Skip next character
+                    i++;
+                }
+
+                result.push(i + 1);
+            }
+
+            i++;
+        }
+
+        return result;
+    }
+
+    /**
+     * Saves line number for parsed rules
+     * @param {String} text CSS stylesheet
+     * @param {rule} rule_node Rule node
+     * @return {rule[]}
+     */
+    function saveLineNumbers(text, rule_node, line_indexes, startLine) {
+        preprocessRules(text, rule_node);
+
+        startLine = startLine || 0;
+
+        // remember lines start indexes, preserving line ending characters
+        if (!line_indexes)
+            var line_indexes = saveLineIndexes(text);
+
+        // now find each rule's line
+        for (var i = 0, il = rule_node.children.length; i < il; i++) {
+            var r = rule_node.children[i];
+            r.line = line_indexes.length + startLine;
+            for (var j = 0, jl = line_indexes.length - 1; j < jl; j++) {
+                var line_ix = line_indexes[j];
+                if (r.start >=  line_indexes[j] && r.start <  line_indexes[j + 1]) {
+                    r.line = j + 1 + startLine;
+                    break;
+                }
+            }
+
+            saveLineNumbers(text, r, line_indexes);
+        }
+
+        return rule_node;
+    }
+
+    return {
+        /**
+         * Parses text as CSS stylesheet, remembring each rule position inside
+         * text
+         * @param {String} text CSS stylesheet to parse
+         */
+        read: function(text, startLine) {
+            var rule_start = [],
+                rule_body_start = [],
+                rules = [],
+                in_comment = 0,
+                root = rule(),
+                cur_parent = root,
+                last_rule = null,
+                stack = [],
+                ch, ch2;
+
+            stack.last = function() {
+                return this[this.length - 1];
+            };
+
+            function hasStr(pos, substr) {
+                return text.substr(pos, substr.length) == substr;
+            }
+
+            for (var i = 0, il = text.length; i < il; i++) {
+                ch = text.charAt(i);
+                ch2 = i < il - 1 ? text.charAt(i + 1) : '';
+
+                if (!rule_start.length)
+                    rule_start.push(i);
+
+                switch (ch) {
+                    case '@':
+                        if (!in_comment) {
+                            if (hasStr(i, '@import')) {
+                                var m = text.substr(i).match(/^@import\s*url\((['"])?.+?\1?\)\;?/);
+                                if (m) {
+                                    cur_parent.addChild(i, i + 7, i + m[0].length);
+                                    i += m[0].length;
+                                    rule_start.pop();
+                                }
+                                break;
+                            }
+                        }
+                    case '/':
+                        // xxxpedro allowing comment inside comment
+                        if (!in_comment && ch2 == '*') { // comment start
+                            in_comment++;
+                        }
+                        break;
+
+                    case '*':
+                        if (ch2 == '/') { // comment end
+                            in_comment--;
+                        }
+                        break;
+
+                    case '{':
+                        if (!in_comment) {
+                            rule_body_start.push(i);
+
+                            cur_parent = cur_parent.addChild(rule_start.pop());
+                            stack.push(cur_parent);
+                        }
+                        break;
+
+                    case '}':
+                        // found the end of the rule
+                        if (!in_comment) {
+                            /** @type {rule} */
+                            var last_rule = stack.pop();
+                            rule_start.pop();
+                            last_rule.body_start = rule_body_start.pop();
+                            last_rule.end = i;
+                            cur_parent = last_rule.parent || root;
+                        }
+                        break;
+                }
+
+            }
+
+            return saveLineNumbers(text, root, null, startLine);
+        },
+
+        normalizeSelector: normalizeSelector,
+
+        /**
+         * Find matched rule by selector.
+         * @param {rule} rule_node Parsed rule node
+         * @param {String} selector CSS selector
+         * @param {String} source CSS stylesheet source code
+         *
+         * @return {rule[]|null} Array of matched rules, sorted by priority (most
+         * recent on top)
+         */
+        findBySelector: function(rule_node, selector, source) {
+            var selector = normalizeSelector(selector),
+                result = [];
+
+            if (rule_node) {
+                for (var i = 0, il = rule_node.children.length; i < il; i++) {
+                    /** @type {rule} */
+                    var r = rule_node.children[i];
+                    if (r.selector == selector) {
+                        result.push(r);
+                    }
+                }
+            }
+
+            if (result.length) {
+                return result;
+            } else {
+                return null;
+            }
+        }
+    };
+})();
+
+
+// ************************************************************************************************
+
+FBL.CssParser = CssParser;
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// StyleSheet Parser
+
+var CssAnalyzer = {};
+
+// ************************************************************************************************
+// Locals
+
+var CSSRuleMap = {};
+var ElementCSSRulesMap = {};
+
+var internalStyleSheetIndex = -1;
+
+var reSelectorTag = /(^|\s)(?:\w+)/g;
+var reSelectorClass = /\.[\w\d_-]+/g;
+var reSelectorId = /#[\w\d_-]+/g;
+
+var globalCSSRuleIndex;
+
+var processAllStyleSheetsTimeout = null;
+
+var externalStyleSheetURLs = [];
+
+var ElementCache = Firebug.Lite.Cache.Element;
+var StyleSheetCache = Firebug.Lite.Cache.StyleSheet;
+
+//************************************************************************************************
+// CSS Analyzer templates
+
+CssAnalyzer.externalStyleSheetWarning = domplate(Firebug.Rep,
+{
+    tag:
+        DIV({"class": "warning focusRow", style: "font-weight:normal;", role: 'listitem'},
+            SPAN("$object|STR"),
+            A({"href": "$href", target:"_blank"}, "$link|STR")
+        )
+});
+
+// ************************************************************************************************
+// CSS Analyzer methods
+
+CssAnalyzer.processAllStyleSheets = function(doc, styleSheetIterator)
+{
+    try
+    {
+        processAllStyleSheets(doc, styleSheetIterator);
+    }
+    catch(e)
+    {
+        // TODO: FBTrace condition
+        FBTrace.sysout("CssAnalyzer.processAllStyleSheets fails: ", e);
+    }
+};
+
+/**
+ *
+ * @param element
+ * @returns {String[]} Array of IDs of CSS Rules
+ */
+CssAnalyzer.getElementCSSRules = function(element)
+{
+    try
+    {
+        return getElementCSSRules(element);
+    }
+    catch(e)
+    {
+        // TODO: FBTrace condition
+        FBTrace.sysout("CssAnalyzer.getElementCSSRules fails: ", e);
+    }
+};
+
+CssAnalyzer.getRuleData = function(ruleId)
+{
+    return CSSRuleMap[ruleId];
+};
+
+// TODO: do we need this?
+CssAnalyzer.getRuleLine = function()
+{
+};
+
+CssAnalyzer.hasExternalStyleSheet = function()
+{
+    return externalStyleSheetURLs.length > 0;
+};
+
+CssAnalyzer.parseStyleSheet = function(href)
+{
+    var sourceData = extractSourceData(href);
+    var parsedObj = CssParser.read(sourceData.source, sourceData.startLine);
+    var parsedRules = parsedObj.children;
+
+    // See: Issue 4776: [Firebug lite] CSS Media Types
+    //
+    // Ignore all special selectors like @media and @page
+    for(var i=0; i < parsedRules.length; )
+    {
+        if (parsedRules[i].selector.indexOf("@") != -1)
+        {
+            parsedRules.splice(i, 1);
+        }
+        else
+            i++;
+    }
+
+    return parsedRules;
+};
+
+//************************************************************************************************
+// Internals
+//************************************************************************************************
+
+// ************************************************************************************************
+// StyleSheet processing
+
+var processAllStyleSheets = function(doc, styleSheetIterator)
+{
+    styleSheetIterator = styleSheetIterator || processStyleSheet;
+
+    globalCSSRuleIndex = -1;
+
+    var styleSheets = doc.styleSheets;
+    var importedStyleSheets = [];
+
+    if (FBTrace.DBG_CSS)
+        var start = new Date().getTime();
+
+    for(var i=0, length=styleSheets.length; i<length; i++)
+    {
+        try
+        {
+            var styleSheet = styleSheets[i];
+
+            if ("firebugIgnore" in styleSheet) continue;
+
+            // we must read the length to make sure we have permission to read
+            // the stylesheet's content. If an error occurs here, we cannot
+            // read the stylesheet due to access restriction policy
+            var rules = isIE ? styleSheet.rules : styleSheet.cssRules;
+            rules.length;
+        }
+        catch(e)
+        {
+            externalStyleSheetURLs.push(styleSheet.href);
+            styleSheet.restricted = true;
+            var ssid = StyleSheetCache(styleSheet);
+
+            /// TODO: xxxpedro external css
+            //loadExternalStylesheet(doc, styleSheetIterator, styleSheet);
+        }
+
+        // process internal and external styleSheets
+        styleSheetIterator(doc, styleSheet);
+
+        var importedStyleSheet, importedRules;
+
+        // process imported styleSheets in IE
+        if (isIE)
+        {
+            var imports = styleSheet.imports;
+
+            for(var j=0, importsLength=imports.length; j<importsLength; j++)
+            {
+                try
+                {
+                    importedStyleSheet = imports[j];
+                    // we must read the length to make sure we have permission
+                    // to read the imported stylesheet's content.
+                    importedRules = importedStyleSheet.rules;
+                    importedRules.length;
+                }
+                catch(e)
+                {
+                    externalStyleSheetURLs.push(styleSheet.href);
+                    importedStyleSheet.restricted = true;
+                    var ssid = StyleSheetCache(importedStyleSheet);
+                }
+
+                styleSheetIterator(doc, importedStyleSheet);
+            }
+        }
+        // process imported styleSheets in other browsers
+        else if (rules)
+        {
+            for(var j=0, rulesLength=rules.length; j<rulesLength; j++)
+            {
+                try
+                {
+                    var rule = rules[j];
+
+                    importedStyleSheet = rule.styleSheet;
+
+                    if (importedStyleSheet)
+                    {
+                        // we must read the length to make sure we have permission
+                        // to read the imported stylesheet's content.
+                        importedRules = importedStyleSheet.cssRules;
+                        importedRules.length;
+                    }
+                    else
+                        break;
+                }
+                catch(e)
+                {
+                    externalStyleSheetURLs.push(styleSheet.href);
+                    importedStyleSheet.restricted = true;
+                    var ssid = StyleSheetCache(importedStyleSheet);
+                }
+
+                styleSheetIterator(doc, importedStyleSheet);
+            }
+        }
+    };
+
+    if (FBTrace.DBG_CSS)
+    {
+        FBTrace.sysout("FBL.processAllStyleSheets", "all stylesheet rules processed in " + (new Date().getTime() - start) + "ms");
+    }
+};
+
+// ************************************************************************************************
+
+var processStyleSheet = function(doc, styleSheet)
+{
+    if (styleSheet.restricted)
+        return;
+
+    var rules = isIE ? styleSheet.rules : styleSheet.cssRules;
+
+    var ssid = StyleSheetCache(styleSheet);
+
+    var href = styleSheet.href;
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // CSS Parser
+    var shouldParseCSS = typeof CssParser != "undefined" && !Firebug.disableResourceFetching;
+    if (shouldParseCSS)
+    {
+        try
+        {
+            var parsedRules = CssAnalyzer.parseStyleSheet(href);
+        }
+        catch(e)
+        {
+            if (FBTrace.DBG_ERRORS) FBTrace.sysout("processStyleSheet FAILS", e.message || e);
+            shouldParseCSS = false;
+        }
+        finally
+        {
+            var parsedRulesIndex = 0;
+
+            var dontSupportGroupedRules = isIE && browserVersion < 9;
+            var group = [];
+            var groupItem;
+        }
+    }
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    for (var i=0, length=rules.length; i<length; i++)
+    {
+        // TODO: xxxpedro is there a better way to cache CSS Rules? The problem is that
+        // we cannot add expando properties in the rule object in IE
+        var rid = ssid + ":" + i;
+        var rule = rules[i];
+        var selector = rule.selectorText || "";
+        var lineNo = null;
+
+        // See: Issue 4776: [Firebug lite] CSS Media Types
+        //
+        // Ignore all special selectors like @media and @page
+        if (!selector || selector.indexOf("@") != -1)
+            continue;
+
+        if (isIE)
+            selector = selector.replace(reSelectorTag, function(s){return s.toLowerCase();});
+
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+        // CSS Parser
+        if (shouldParseCSS)
+        {
+            var parsedRule = parsedRules[parsedRulesIndex];
+            var parsedSelector = parsedRule.selector;
+
+            if (dontSupportGroupedRules && parsedSelector.indexOf(",") != -1 && group.length == 0)
+                group = parsedSelector.split(",");
+
+            if (dontSupportGroupedRules && group.length > 0)
+            {
+                groupItem = group.shift();
+
+                if (CssParser.normalizeSelector(selector) == groupItem)
+                    lineNo = parsedRule.line;
+
+                if (group.length == 0)
+                    parsedRulesIndex++;
+            }
+            else if (CssParser.normalizeSelector(selector) == parsedRule.selector)
+            {
+                lineNo = parsedRule.line;
+                parsedRulesIndex++;
+            }
+        }
+        // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+        CSSRuleMap[rid] =
+        {
+            styleSheetId: ssid,
+            styleSheetIndex: i,
+            order: ++globalCSSRuleIndex,
+            specificity:
+                // See: Issue 4777: [Firebug lite] Specificity of CSS Rules
+                //
+                // if it is a normal selector then calculate the specificity
+                selector && selector.indexOf(",") == -1 ?
+                getCSSRuleSpecificity(selector) :
+                // See: Issue 3262: [Firebug lite] Specificity of grouped CSS Rules
+                //
+                // if it is a grouped selector, do not calculate the specificity
+                // because the correct value will depend of the matched element.
+                // The proper specificity value for grouped selectors are calculated
+                // via getElementCSSRules(element)
+                0,
+
+            rule: rule,
+            lineNo: lineNo,
+            selector: selector,
+            cssText: rule.style ? rule.style.cssText : rule.cssText ? rule.cssText : ""
+        };
+
+        // TODO: what happens with elements added after this? Need to create a test case.
+        // Maybe we should place this at getElementCSSRules() but it will make the function
+        // a lot more expensive.
+        //
+        // Maybe add a "refresh" button?
+        var elements = Firebug.Selector(selector, doc);
+
+        for (var j=0, elementsLength=elements.length; j<elementsLength; j++)
+        {
+            var element = elements[j];
+            var eid = ElementCache(element);
+
+            if (!ElementCSSRulesMap[eid])
+                ElementCSSRulesMap[eid] = [];
+
+            ElementCSSRulesMap[eid].push(rid);
+        }
+
+        //console.log(selector, elements);
+    }
+};
+
+// ************************************************************************************************
+// External StyleSheet Loader
+
+var loadExternalStylesheet = function(doc, styleSheetIterator, styleSheet)
+{
+    var url = styleSheet.href;
+    styleSheet.firebugIgnore = true;
+
+    var source = Firebug.Lite.Proxy.load(url);
+
+    // TODO: check for null and error responses
+
+    // remove comments
+    //var reMultiComment = /(\/\*([^\*]|\*(?!\/))*\*\/)/g;
+    //source = source.replace(reMultiComment, "");
+
+    // convert relative addresses to absolute ones
+    source = source.replace(/url\(([^\)]+)\)/g, function(a,name){
+
+        var hasDomain = /\w+:\/\/./.test(name);
+
+        if (!hasDomain)
+        {
+            name = name.replace(/^(["'])(.+)\1$/, "$2");
+            var first = name.charAt(0);
+
+            // relative path, based on root
+            if (first == "/")
+            {
+                // TODO: xxxpedro move to lib or Firebug.Lite.something
+                // getURLRoot
+                var m = /^([^:]+:\/{1,3}[^\/]+)/.exec(url);
+
+                return m ?
+                    "url(" + m[1] + name + ")" :
+                    "url(" + name + ")";
+            }
+            // relative path, based on current location
+            else
+            {
+                // TODO: xxxpedro move to lib or Firebug.Lite.something
+                // getURLPath
+                var path = url.replace(/[^\/]+\.[\w\d]+(\?.+|#.+)?$/g, "");
+
+                path = path + name;
+
+                var reBack = /[^\/]+\/\.\.\//;
+                while(reBack.test(path))
+                {
+                    path = path.replace(reBack, "");
+                }
+
+                //console.log("url(" + path + ")");
+
+                return "url(" + path + ")";
+            }
+        }
+
+        // if it is an absolute path, there is nothing to do
+        return a;
+    });
+
+    var oldStyle = styleSheet.ownerNode;
+
+    if (!oldStyle) return;
+
+    if (!oldStyle.parentNode) return;
+
+    var style = createGlobalElement("style");
+    style.setAttribute("charset","utf-8");
+    style.setAttribute("type", "text/css");
+    style.innerHTML = source;
+
+    //debugger;
+    oldStyle.parentNode.insertBefore(style, oldStyle.nextSibling);
+    oldStyle.parentNode.removeChild(oldStyle);
+
+    doc.styleSheets[doc.styleSheets.length-1].externalURL = url;
+
+    console.log(url, "call " + externalStyleSheetURLs.length, source);
+
+    externalStyleSheetURLs.pop();
+
+    if (processAllStyleSheetsTimeout)
+    {
+        clearTimeout(processAllStyleSheetsTimeout);
+    }
+
+    processAllStyleSheetsTimeout = setTimeout(function(){
+        console.log("processing");
+        FBL.processAllStyleSheets(doc, styleSheetIterator);
+        processAllStyleSheetsTimeout = null;
+    },200);
+
+};
+
+//************************************************************************************************
+// getElementCSSRules
+
+var getElementCSSRules = function(element)
+{
+    var eid = ElementCache(element);
+    var rules = ElementCSSRulesMap[eid];
+
+    if (!rules) return;
+
+    var arr = [element];
+    var Selector = Firebug.Selector;
+    var ruleId, rule;
+
+    // for the case of grouped selectors, we need to calculate the highest
+    // specificity within the selectors of the group that matches the element,
+    // so we can sort the rules properly without over estimating the specificity
+    // of grouped selectors
+    for (var i = 0, length = rules.length; i < length; i++)
+    {
+        ruleId = rules[i];
+        rule = CSSRuleMap[ruleId];
+
+        // check if it is a grouped selector
+        if (rule.selector.indexOf(",") != -1)
+        {
+            var selectors = rule.selector.split(",");
+            var maxSpecificity = -1;
+            var sel, spec, mostSpecificSelector;
+
+            // loop over all selectors in the group
+            for (var j, len = selectors.length; j < len; j++)
+            {
+                sel = selectors[j];
+
+                // find if the selector matches the element
+                if (Selector.matches(sel, arr).length == 1)
+                {
+                    spec = getCSSRuleSpecificity(sel);
+
+                    // find the most specific selector that macthes the element
+                    if (spec > maxSpecificity)
+                    {
+                        maxSpecificity = spec;
+                        mostSpecificSelector = sel;
+                    }
+                }
+            }
+
+            rule.specificity = maxSpecificity;
+        }
+    }
+
+    rules.sort(sortElementRules);
+    //rules.sort(solveRulesTied);
+
+    return rules;
+};
+
+// ************************************************************************************************
+// Rule Specificity
+
+var sortElementRules = function(a, b)
+{
+    var ruleA = CSSRuleMap[a];
+    var ruleB = CSSRuleMap[b];
+
+    var specificityA = ruleA.specificity;
+    var specificityB = ruleB.specificity;
+
+    if (specificityA > specificityB)
+        return 1;
+
+    else if (specificityA < specificityB)
+        return -1;
+
+    else
+        return ruleA.order > ruleB.order ? 1 : -1;
+};
+
+var solveRulesTied = function(a, b)
+{
+    var ruleA = CSSRuleMap[a];
+    var ruleB = CSSRuleMap[b];
+
+    if (ruleA.specificity == ruleB.specificity)
+        return ruleA.order > ruleB.order ? 1 : -1;
+
+    return null;
+};
+
+var getCSSRuleSpecificity = function(selector)
+{
+    var match = selector.match(reSelectorTag);
+    var tagCount = match ? match.length : 0;
+
+    match = selector.match(reSelectorClass);
+    var classCount = match ? match.length : 0;
+
+    match = selector.match(reSelectorId);
+    var idCount = match ? match.length : 0;
+
+    return tagCount + 10*classCount + 100*idCount;
+};
+
+// ************************************************************************************************
+// StyleSheet data
+
+var extractSourceData = function(href)
+{
+    var sourceData =
+    {
+        source: null,
+        startLine: 0
+    };
+
+    if (href)
+    {
+        sourceData.source = Firebug.Lite.Proxy.load(href);
+    }
+    else
+    {
+        // TODO: create extractInternalSourceData(index)
+        // TODO: pre process the position of the inline styles so this will happen only once
+        // in case of having multiple inline styles
+        var index = 0;
+        var ssIndex = ++internalStyleSheetIndex;
+        var reStyleTag = /\<\s*style.*\>/gi;
+        var reEndStyleTag = /\<\/\s*style.*\>/gi;
+
+        var source = Firebug.Lite.Proxy.load(Env.browser.location.href);
+        source = source.replace(/\n\r|\r\n/g, "\n"); // normalize line breaks
+
+        var startLine = 0;
+
+        do
+        {
+            var matchStyleTag = source.match(reStyleTag);
+            var i0 = source.indexOf(matchStyleTag[0]) + matchStyleTag[0].length;
+
+            for (var i=0; i < i0; i++)
+            {
+                if (source.charAt(i) == "\n")
+                    startLine++;
+            }
+
+            source = source.substr(i0);
+
+            index++;
+        }
+        while (index <= ssIndex);
+
+        var matchEndStyleTag = source.match(reEndStyleTag);
+        var i1 = source.indexOf(matchEndStyleTag[0]);
+
+        var extractedSource = source.substr(0, i1);
+
+        sourceData.source = extractedSource;
+        sourceData.startLine = startLine;
+    }
+
+    return sourceData;
+};
+
+// ************************************************************************************************
+// Registration
+
+FBL.CssAnalyzer = CssAnalyzer;
+
+// ************************************************************************************************
+}});
+
+
+/* See license.txt for terms of usage */
+
+// move to FBL
+(function() {
+
+// ************************************************************************************************
+// XPath
+
+/**
+ * Gets an XPath for an element which describes its hierarchical location.
+ */
+this.getElementXPath = function(element)
+{
+    try
+    {
+        if (element && element.id)
+            return '//*[@id="' + element.id + '"]';
+        else
+            return this.getElementTreeXPath(element);
+    }
+    catch(E)
+    {
+        // xxxpedro: trying to detect the mysterious error:
+        // Security error" code: "1000
+        //debugger;
+    }
+};
+
+this.getElementTreeXPath = function(element)
+{
+    var paths = [];
+
+    for (; element && element.nodeType == 1; element = element.parentNode)
+    {
+        var index = 0;
+        var nodeName = element.nodeName;
+
+        for (var sibling = element.previousSibling; sibling; sibling = sibling.previousSibling)
+        {
+            if (sibling.nodeType != 1) continue;
+
+            if (sibling.nodeName == nodeName)
+                ++index;
+        }
+
+        var tagName = element.nodeName.toLowerCase();
+        var pathIndex = (index ? "[" + (index+1) + "]" : "");
+        paths.splice(0, 0, tagName + pathIndex);
+    }
+
+    return paths.length ? "/" + paths.join("/") : null;
+};
+
+this.getElementsByXPath = function(doc, xpath)
+{
+    var nodes = [];
+
+    try {
+        var result = doc.evaluate(xpath, doc, null, XPathResult.ANY_TYPE, null);
+        for (var item = result.iterateNext(); item; item = result.iterateNext())
+            nodes.push(item);
+    }
+    catch (exc)
+    {
+        // Invalid xpath expressions make their way here sometimes.  If that happens,
+        // we still want to return an empty set without an exception.
+    }
+
+    return nodes;
+};
+
+this.getRuleMatchingElements = function(rule, doc)
+{
+    var css = rule.selectorText;
+    var xpath = this.cssToXPath(css);
+    return this.getElementsByXPath(doc, xpath);
+};
+
+
+}).call(FBL);
+
+
+
+
+FBL.ns(function() { with (FBL) {
+
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+
+var toCamelCase = function toCamelCase(s)
+{
+    return s.replace(reSelectorCase, toCamelCaseReplaceFn);
+};
+
+var toSelectorCase = function toSelectorCase(s)
+{
+  return s.replace(reCamelCase, "-$1").toLowerCase();
+
+};
+
+var reCamelCase = /([A-Z])/g;
+var reSelectorCase = /\-(.)/g;
+var toCamelCaseReplaceFn = function toCamelCaseReplaceFn(m,g)
+{
+    return g.toUpperCase();
+};
+
+// ************************************************************************************************
+
+var ElementCache = Firebug.Lite.Cache.Element;
+var StyleSheetCache = Firebug.Lite.Cache.StyleSheet;
+
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+// ************************************************************************************************
+
+
+// ************************************************************************************************
+// Constants
+
+//const Cc = Components.classes;
+//const Ci = Components.interfaces;
+//const nsIDOMCSSStyleRule = Ci.nsIDOMCSSStyleRule;
+//const nsIInterfaceRequestor = Ci.nsIInterfaceRequestor;
+//const nsISelectionDisplay = Ci.nsISelectionDisplay;
+//const nsISelectionController = Ci.nsISelectionController;
+
+// See: http://mxr.mozilla.org/mozilla1.9.2/source/content/events/public/nsIEventStateManager.h#153
+//const STATE_ACTIVE  = 0x01;
+//const STATE_FOCUS   = 0x02;
+//const STATE_HOVER   = 0x04;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+Firebug.SourceBoxPanel = Firebug.Panel;
+
+var reSelectorTag = /(^|\s)(?:\w+)/g;
+
+var domUtils = null;
+
+var textContent = isIE ? "innerText" : "textContent";
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var CSSDomplateBase = {
+    isEditable: function(rule)
+    {
+        return !rule.isSystemSheet;
+    },
+    isSelectorEditable: function(rule)
+    {
+        return rule.isSelectorEditable && this.isEditable(rule);
+    }
+};
+
+var CSSPropTag = domplate(CSSDomplateBase, {
+    tag: DIV({"class": "cssProp focusRow", $disabledStyle: "$prop.disabled",
+          $editGroup: "$rule|isEditable",
+          $cssOverridden: "$prop.overridden", role : "option"},
+        A({"class": "cssPropDisable"}, "&nbsp;&nbsp;"),
+        SPAN({"class": "cssPropName", $editable: "$rule|isEditable"}, "$prop.name"),
+        SPAN({"class": "cssColon"}, ":"),
+        SPAN({"class": "cssPropValue", $editable: "$rule|isEditable"}, "$prop.value$prop.important"),
+        SPAN({"class": "cssSemi"}, ";")
+    )
+});
+
+var CSSRuleTag =
+    TAG("$rule.tag", {rule: "$rule"});
+
+var CSSImportRuleTag = domplate({
+    tag: DIV({"class": "cssRule insertInto focusRow importRule", _repObject: "$rule.rule"},
+        "@import &quot;",
+        A({"class": "objectLink", _repObject: "$rule.rule.styleSheet"}, "$rule.rule.href"),
+        "&quot;;"
+    )
+});
+
+var CSSStyleRuleTag = domplate(CSSDomplateBase, {
+    tag: DIV({"class": "cssRule insertInto",
+            $cssEditableRule: "$rule|isEditable",
+            $editGroup: "$rule|isSelectorEditable",
+            _repObject: "$rule.rule",
+            "ruleId": "$rule.id", role : 'presentation'},
+        DIV({"class": "cssHead focusRow", role : 'listitem'},
+            SPAN({"class": "cssSelector", $editable: "$rule|isSelectorEditable"}, "$rule.selector"), " {"
+        ),
+        DIV({role : 'group'},
+            DIV({"class": "cssPropertyListBox", role : 'listbox'},
+                FOR("prop", "$rule.props",
+                    TAG(CSSPropTag.tag, {rule: "$rule", prop: "$prop"})
+                )
+            )
+        ),
+        DIV({"class": "editable insertBefore", role:"presentation"}, "}")
+    )
+});
+
+var reSplitCSS =  /(url\("?[^"\)]+?"?\))|(rgb\(.*?\))|(#[\dA-Fa-f]+)|(-?\d+(\.\d+)?(%|[a-z]{1,2})?)|([^,\s]+)|"(.*?)"/;
+
+var reURL = /url\("?([^"\)]+)?"?\)/;
+
+var reRepeat = /no-repeat|repeat-x|repeat-y|repeat/;
+
+//const sothinkInstalled = !!$("swfcatcherKey_sidebar");
+var sothinkInstalled = false;
+var styleGroups =
+{
+    text: [
+        "font-family",
+        "font-size",
+        "font-weight",
+        "font-style",
+        "color",
+        "text-transform",
+        "text-decoration",
+        "letter-spacing",
+        "word-spacing",
+        "line-height",
+        "text-align",
+        "vertical-align",
+        "direction",
+        "column-count",
+        "column-gap",
+        "column-width"
+    ],
+
+    background: [
+        "background-color",
+        "background-image",
+        "background-repeat",
+        "background-position",
+        "background-attachment",
+        "opacity"
+    ],
+
+    box: [
+        "width",
+        "height",
+        "top",
+        "right",
+        "bottom",
+        "left",
+        "margin-top",
+        "margin-right",
+        "margin-bottom",
+        "margin-left",
+        "padding-top",
+        "padding-right",
+        "padding-bottom",
+        "padding-left",
+        "border-top-width",
+        "border-right-width",
+        "border-bottom-width",
+        "border-left-width",
+        "border-top-color",
+        "border-right-color",
+        "border-bottom-color",
+        "border-left-color",
+        "border-top-style",
+        "border-right-style",
+        "border-bottom-style",
+        "border-left-style",
+        "-moz-border-top-radius",
+        "-moz-border-right-radius",
+        "-moz-border-bottom-radius",
+        "-moz-border-left-radius",
+        "outline-top-width",
+        "outline-right-width",
+        "outline-bottom-width",
+        "outline-left-width",
+        "outline-top-color",
+        "outline-right-color",
+        "outline-bottom-color",
+        "outline-left-color",
+        "outline-top-style",
+        "outline-right-style",
+        "outline-bottom-style",
+        "outline-left-style"
+    ],
+
+    layout: [
+        "position",
+        "display",
+        "visibility",
+        "z-index",
+        "overflow-x",  // http://www.w3.org/TR/2002/WD-css3-box-20021024/#overflow
+        "overflow-y",
+        "overflow-clip",
+        "white-space",
+        "clip",
+        "float",
+        "clear",
+        "-moz-box-sizing"
+    ],
+
+    other: [
+        "cursor",
+        "list-style-image",
+        "list-style-position",
+        "list-style-type",
+        "marker-offset",
+        "user-focus",
+        "user-select",
+        "user-modify",
+        "user-input"
+    ]
+};
+
+var styleGroupTitles =
+{
+    text: "Text",
+    background: "Background",
+    box: "Box Model",
+    layout: "Layout",
+    other: "Other"
+};
+
+Firebug.CSSModule = extend(Firebug.Module,
+{
+    freeEdit: function(styleSheet, value)
+    {
+        if (!styleSheet.editStyleSheet)
+        {
+            var ownerNode = getStyleSheetOwnerNode(styleSheet);
+            styleSheet.disabled = true;
+
+            var url = CCSV("@mozilla.org/network/standard-url;1", Components.interfaces.nsIURL);
+            url.spec = styleSheet.href;
+
+            var editStyleSheet = ownerNode.ownerDocument.createElementNS(
+                "http://www.w3.org/1999/xhtml",
+                "style");
+            unwrapObject(editStyleSheet).firebugIgnore = true;
+            editStyleSheet.setAttribute("type", "text/css");
+            editStyleSheet.setAttributeNS(
+                "http://www.w3.org/XML/1998/namespace",
+                "base",
+                url.directory);
+            if (ownerNode.hasAttribute("media"))
+            {
+              editStyleSheet.setAttribute("media", ownerNode.getAttribute("media"));
+            }
+
+            // Insert the edited stylesheet directly after the old one to ensure the styles
+            // cascade properly.
+            ownerNode.parentNode.insertBefore(editStyleSheet, ownerNode.nextSibling);
+
+            styleSheet.editStyleSheet = editStyleSheet;
+        }
+
+        styleSheet.editStyleSheet.innerHTML = value;
+        if (FBTrace.DBG_CSS)
+            FBTrace.sysout("css.saveEdit styleSheet.href:"+styleSheet.href+" got innerHTML:"+value+"\n");
+
+        dispatch(this.fbListeners, "onCSSFreeEdit", [styleSheet, value]);
+    },
+
+    insertRule: function(styleSheet, cssText, ruleIndex)
+    {
+        if (FBTrace.DBG_CSS) FBTrace.sysout("Insert: " + ruleIndex + " " + cssText);
+        var insertIndex = styleSheet.insertRule(cssText, ruleIndex);
+
+        dispatch(this.fbListeners, "onCSSInsertRule", [styleSheet, cssText, ruleIndex]);
+
+        return insertIndex;
+    },
+
+    deleteRule: function(styleSheet, ruleIndex)
+    {
+        if (FBTrace.DBG_CSS) FBTrace.sysout("deleteRule: " + ruleIndex + " " + styleSheet.cssRules.length, styleSheet.cssRules);
+        dispatch(this.fbListeners, "onCSSDeleteRule", [styleSheet, ruleIndex]);
+
+        styleSheet.deleteRule(ruleIndex);
+    },
+
+    setProperty: function(rule, propName, propValue, propPriority)
+    {
+        var style = rule.style || rule;
+
+        // Record the original CSS text for the inline case so we can reconstruct at a later
+        // point for diffing purposes
+        var baseText = style.cssText;
+
+        // good browsers
+        if (style.getPropertyValue)
+        {
+            var prevValue = style.getPropertyValue(propName);
+            var prevPriority = style.getPropertyPriority(propName);
+
+            // XXXjoe Gecko bug workaround: Just changing priority doesn't have any effect
+            // unless we remove the property first
+            style.removeProperty(propName);
+
+            style.setProperty(propName, propValue, propPriority);
+        }
+        // sad browsers
+        else
+        {
+            // TODO: xxxpedro parse CSS rule to find property priority in IE?
+            //console.log(propName, propValue);
+            style[toCamelCase(propName)] = propValue;
+        }
+
+        if (propName) {
+            dispatch(this.fbListeners, "onCSSSetProperty", [style, propName, propValue, propPriority, prevValue, prevPriority, rule, baseText]);
+        }
+    },
+
+    removeProperty: function(rule, propName, parent)
+    {
+        var style = rule.style || rule;
+
+        // Record the original CSS text for the inline case so we can reconstruct at a later
+        // point for diffing purposes
+        var baseText = style.cssText;
+
+        if (style.getPropertyValue)
+        {
+
+            var prevValue = style.getPropertyValue(propName);
+            var prevPriority = style.getPropertyPriority(propName);
+
+            style.removeProperty(propName);
+        }
+        else
+        {
+            style[toCamelCase(propName)] = "";
+        }
+
+        if (propName) {
+            dispatch(this.fbListeners, "onCSSRemoveProperty", [style, propName, prevValue, prevPriority, rule, baseText]);
+        }
+    }/*,
+
+    cleanupSheets: function(doc, context)
+    {
+        // Due to the manner in which the layout engine handles multiple
+        // references to the same sheet we need to kick it a little bit.
+        // The injecting a simple stylesheet then removing it will force
+        // Firefox to regenerate it's CSS hierarchy.
+        //
+        // WARN: This behavior was determined anecdotally.
+        // See http://code.google.com/p/fbug/issues/detail?id=2440
+        var style = doc.createElementNS("http://www.w3.org/1999/xhtml", "style");
+        style.setAttribute("charset","utf-8");
+        unwrapObject(style).firebugIgnore = true;
+        style.setAttribute("type", "text/css");
+        style.innerHTML = "#fbIgnoreStyleDO_NOT_USE {}";
+        addStyleSheet(doc, style);
+        style.parentNode.removeChild(style);
+
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=500365
+        // This voodoo touches each style sheet to force some Firefox internal change to allow edits.
+        var styleSheets = getAllStyleSheets(context);
+        for(var i = 0; i < styleSheets.length; i++)
+        {
+            try
+            {
+                var rules = styleSheets[i].cssRules;
+                if (rules.length > 0)
+                    var touch = rules[0];
+                if (FBTrace.DBG_CSS && touch)
+                    FBTrace.sysout("css.show() touch "+typeof(touch)+" in "+(styleSheets[i].href?styleSheets[i].href:context.getName()));
+            }
+            catch(e)
+            {
+                if (FBTrace.DBG_ERRORS)
+                    FBTrace.sysout("css.show: sheet.cssRules FAILS for "+(styleSheets[i]?styleSheets[i].href:"null sheet")+e, e);
+            }
+        }
+    },
+    cleanupSheetHandler: function(event, context)
+    {
+        var target = event.target || event.srcElement,
+            tagName = (target.tagName || "").toLowerCase();
+        if (tagName == "link")
+        {
+            this.cleanupSheets(target.ownerDocument, context);
+        }
+    },
+    watchWindow: function(context, win)
+    {
+        var cleanupSheets = bind(this.cleanupSheets, this),
+            cleanupSheetHandler = bind(this.cleanupSheetHandler, this, context),
+            doc = win.document;
+
+        //doc.addEventListener("DOMAttrModified", cleanupSheetHandler, false);
+        //doc.addEventListener("DOMNodeInserted", cleanupSheetHandler, false);
+    },
+    loadedContext: function(context)
+    {
+        var self = this;
+        iterateWindows(context.browser.contentWindow, function(subwin)
+        {
+            self.cleanupSheets(subwin.document, context);
+        });
+    }
+    /**/
+});
+
+// ************************************************************************************************
+
+Firebug.CSSStyleSheetPanel = function() {};
+
+Firebug.CSSStyleSheetPanel.prototype = extend(Firebug.SourceBoxPanel,
+{
+    template: domplate(
+    {
+        tag:
+            DIV({"class": "cssSheet insertInto a11yCSSView"},
+                FOR("rule", "$rules",
+                    CSSRuleTag
+                ),
+                DIV({"class": "cssSheet editable insertBefore"}, "")
+                )
+    }),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    refresh: function()
+    {
+        if (this.location)
+            this.updateLocation(this.location);
+        else if (this.selection)
+            this.updateSelection(this.selection);
+    },
+
+    toggleEditing: function()
+    {
+        if (!this.stylesheetEditor)
+            this.stylesheetEditor = new StyleSheetEditor(this.document);
+
+        if (this.editing)
+            Firebug.Editor.stopEditing();
+        else
+        {
+            if (!this.location)
+                return;
+
+            var styleSheet = this.location.editStyleSheet
+                ? this.location.editStyleSheet.sheet
+                : this.location;
+
+            var css = getStyleSheetCSS(styleSheet, this.context);
+            //var topmost = getTopmostRuleLine(this.panelNode);
+
+            this.stylesheetEditor.styleSheet = this.location;
+            Firebug.Editor.startEditing(this.panelNode, css, this.stylesheetEditor);
+            //this.stylesheetEditor.scrollToLine(topmost.line, topmost.offset);
+        }
+    },
+
+    getStylesheetURL: function(rule)
+    {
+        if (this.location.href)
+            return this.location.href;
+        else
+            return this.context.window.location.href;
+    },
+
+    getRuleByLine: function(styleSheet, line)
+    {
+        if (!domUtils)
+            return null;
+
+        var cssRules = styleSheet.cssRules;
+        for (var i = 0; i < cssRules.length; ++i)
+        {
+            var rule = cssRules[i];
+            if (rule instanceof CSSStyleRule)
+            {
+                var ruleLine = domUtils.getRuleLine(rule);
+                if (ruleLine >= line)
+                    return rule;
+            }
+        }
+    },
+
+    highlightRule: function(rule)
+    {
+        var ruleElement = Firebug.getElementByRepObject(this.panelNode.firstChild, rule);
+        if (ruleElement)
+        {
+            scrollIntoCenterView(ruleElement, this.panelNode);
+            setClassTimed(ruleElement, "jumpHighlight", this.context);
+        }
+    },
+
+    getStyleSheetRules: function(context, styleSheet)
+    {
+        var isSystemSheet = isSystemStyleSheet(styleSheet);
+
+        function appendRules(cssRules)
+        {
+            for (var i = 0; i < cssRules.length; ++i)
+            {
+                var rule = cssRules[i];
+
+                // TODO: xxxpedro opera instanceof stylesheet remove the following comments when
+                // the issue with opera and style sheet Classes has been solved.
+
+                //if (rule instanceof CSSStyleRule)
+                if (instanceOf(rule, "CSSStyleRule"))
+                {
+                    var props = this.getRuleProperties(context, rule);
+                    //var line = domUtils.getRuleLine(rule);
+                    var line = null;
+
+                    var selector = rule.selectorText;
+
+                    if (isIE)
+                    {
+                        selector = selector.replace(reSelectorTag,
+                                function(s){return s.toLowerCase();});
+                    }
+
+                    var ruleId = rule.selectorText+"/"+line;
+                    rules.push({tag: CSSStyleRuleTag.tag, rule: rule, id: ruleId,
+                                selector: selector, props: props,
+                                isSystemSheet: isSystemSheet,
+                                isSelectorEditable: true});
+                }
+                //else if (rule instanceof CSSImportRule)
+                else if (instanceOf(rule, "CSSImportRule"))
+                    rules.push({tag: CSSImportRuleTag.tag, rule: rule});
+                //else if (rule instanceof CSSMediaRule)
+                else if (instanceOf(rule, "CSSMediaRule"))
+                    appendRules.apply(this, [rule.cssRules]);
+                else
+                {
+                    if (FBTrace.DBG_ERRORS || FBTrace.DBG_CSS)
+                        FBTrace.sysout("css getStyleSheetRules failed to classify a rule ", rule);
+                }
+            }
+        }
+
+        var rules = [];
+        appendRules.apply(this, [styleSheet.cssRules || styleSheet.rules]);
+        return rules;
+    },
+
+    parseCSSProps: function(style, inheritMode)
+    {
+        var props = [];
+
+        if (Firebug.expandShorthandProps)
+        {
+            var count = style.length-1,
+                index = style.length;
+            while (index--)
+            {
+                var propName = style.item(count - index);
+                this.addProperty(propName, style.getPropertyValue(propName), !!style.getPropertyPriority(propName), false, inheritMode, props);
+            }
+        }
+        else
+        {
+            var lines = style.cssText.match(/(?:[^;\(]*(?:\([^\)]*?\))?[^;\(]*)*;?/g);
+            var propRE = /\s*([^:\s]*)\s*:\s*(.*?)\s*(! important)?;?$/;
+            var line,i=0;
+            // TODO: xxxpedro port to firebug: variable leaked into global namespace
+            var m;
+
+            while(line=lines[i++]){
+                m = propRE.exec(line);
+                if(!m)
+                    continue;
+                //var name = m[1], value = m[2], important = !!m[3];
+                if (m[2])
+                    this.addProperty(m[1], m[2], !!m[3], false, inheritMode, props);
+            };
+        }
+
+        return props;
+    },
+
+    getRuleProperties: function(context, rule, inheritMode)
+    {
+        var props = this.parseCSSProps(rule.style, inheritMode);
+
+        // TODO: xxxpedro port to firebug: variable leaked into global namespace
+        //var line = domUtils.getRuleLine(rule);
+        var line;
+        var ruleId = rule.selectorText+"/"+line;
+        this.addOldProperties(context, ruleId, inheritMode, props);
+        sortProperties(props);
+
+        return props;
+    },
+
+    addOldProperties: function(context, ruleId, inheritMode, props)
+    {
+        if (context.selectorMap && context.selectorMap.hasOwnProperty(ruleId) )
+        {
+            var moreProps = context.selectorMap[ruleId];
+            for (var i = 0; i < moreProps.length; ++i)
+            {
+                var prop = moreProps[i];
+                this.addProperty(prop.name, prop.value, prop.important, true, inheritMode, props);
+            }
+        }
+    },
+
+    addProperty: function(name, value, important, disabled, inheritMode, props)
+    {
+        name = name.toLowerCase();
+
+        if (inheritMode && !inheritedStyleNames[name])
+            return;
+
+        name = this.translateName(name, value);
+        if (name)
+        {
+            value = stripUnits(rgbToHex(value));
+            important = important ? " !important" : "";
+
+            var prop = {name: name, value: value, important: important, disabled: disabled};
+            props.push(prop);
+        }
+    },
+
+    translateName: function(name, value)
+    {
+        // Don't show these proprietary Mozilla properties
+        if ((value == "-moz-initial"
+            && (name == "-moz-background-clip" || name == "-moz-background-origin"
+                || name == "-moz-background-inline-policy"))
+        || (value == "physical"
+            && (name == "margin-left-ltr-source" || name == "margin-left-rtl-source"
+                || name == "margin-right-ltr-source" || name == "margin-right-rtl-source"))
+        || (value == "physical"
+            && (name == "padding-left-ltr-source" || name == "padding-left-rtl-source"
+                || name == "padding-right-ltr-source" || name == "padding-right-rtl-source")))
+            return null;
+
+        // Translate these back to the form the user probably expects
+        if (name == "margin-left-value")
+            return "margin-left";
+        else if (name == "margin-right-value")
+            return "margin-right";
+        else if (name == "margin-top-value")
+            return "margin-top";
+        else if (name == "margin-bottom-value")
+            return "margin-bottom";
+        else if (name == "padding-left-value")
+            return "padding-left";
+        else if (name == "padding-right-value")
+            return "padding-right";
+        else if (name == "padding-top-value")
+            return "padding-top";
+        else if (name == "padding-bottom-value")
+            return "padding-bottom";
+        // XXXjoe What about border!
+        else
+            return name;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    editElementStyle: function()
+    {
+        ///var rulesBox = this.panelNode.getElementsByClassName("cssElementRuleContainer")[0];
+        var rulesBox = $$(".cssElementRuleContainer", this.panelNode)[0];
+        var styleRuleBox = rulesBox && Firebug.getElementByRepObject(rulesBox, this.selection);
+        if (!styleRuleBox)
+        {
+            var rule = {rule: this.selection, inherited: false, selector: "element.style", props: []};
+            if (!rulesBox)
+            {
+                // The element did not have any displayed styles. We need to create the whole tree and remove
+                // the no styles message
+                styleRuleBox = this.template.cascadedTag.replace({
+                    rules: [rule], inherited: [], inheritLabel: "Inherited from" // $STR("InheritedFrom")
+                }, this.panelNode);
+
+                ///styleRuleBox = styleRuleBox.getElementsByClassName("cssElementRuleContainer")[0];
+                styleRuleBox = $$(".cssElementRuleContainer", styleRuleBox)[0];
+            }
+            else
+                styleRuleBox = this.template.ruleTag.insertBefore({rule: rule}, rulesBox);
+
+            ///styleRuleBox = styleRuleBox.getElementsByClassName("insertInto")[0];
+            styleRuleBox = $$(".insertInto", styleRuleBox)[0];
+        }
+
+        Firebug.Editor.insertRowForObject(styleRuleBox);
+    },
+
+    insertPropertyRow: function(row)
+    {
+        Firebug.Editor.insertRowForObject(row);
+    },
+
+    insertRule: function(row)
+    {
+        var location = getAncestorByClass(row, "cssRule");
+        if (!location)
+        {
+            location = getChildByClass(this.panelNode, "cssSheet");
+            Firebug.Editor.insertRowForObject(location);
+        }
+        else
+        {
+            Firebug.Editor.insertRow(location, "before");
+        }
+    },
+
+    editPropertyRow: function(row)
+    {
+        var propValueBox = getChildByClass(row, "cssPropValue");
+        Firebug.Editor.startEditing(propValueBox);
+    },
+
+    deletePropertyRow: function(row)
+    {
+        var rule = Firebug.getRepObject(row);
+        var propName = getChildByClass(row, "cssPropName")[textContent];
+        Firebug.CSSModule.removeProperty(rule, propName);
+
+        // Remove the property from the selector map, if it was disabled
+        var ruleId = Firebug.getRepNode(row).getAttribute("ruleId");
+        if ( this.context.selectorMap && this.context.selectorMap.hasOwnProperty(ruleId) )
+        {
+            var map = this.context.selectorMap[ruleId];
+            for (var i = 0; i < map.length; ++i)
+            {
+                if (map[i].name == propName)
+                {
+                    map.splice(i, 1);
+                    break;
+                }
+            }
+        }
+        if (this.name == "stylesheet")
+            dispatch([Firebug.A11yModel], 'onInlineEditorClose', [this, row.firstChild, true]);
+        row.parentNode.removeChild(row);
+
+        this.markChange(this.name == "stylesheet");
+    },
+
+    disablePropertyRow: function(row)
+    {
+        toggleClass(row, "disabledStyle");
+
+        var rule = Firebug.getRepObject(row);
+        var propName = getChildByClass(row, "cssPropName")[textContent];
+
+        if (!this.context.selectorMap)
+            this.context.selectorMap = {};
+
+        // XXXjoe Generate unique key for elements too
+        var ruleId = Firebug.getRepNode(row).getAttribute("ruleId");
+        if (!(this.context.selectorMap.hasOwnProperty(ruleId)))
+            this.context.selectorMap[ruleId] = [];
+
+        var map = this.context.selectorMap[ruleId];
+        var propValue = getChildByClass(row, "cssPropValue")[textContent];
+        var parsedValue = parsePriority(propValue);
+        if (hasClass(row, "disabledStyle"))
+        {
+            Firebug.CSSModule.removeProperty(rule, propName);
+
+            map.push({"name": propName, "value": parsedValue.value,
+                "important": parsedValue.priority});
+        }
+        else
+        {
+            Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority);
+
+            var index = findPropByName(map, propName);
+            map.splice(index, 1);
+        }
+
+        this.markChange(this.name == "stylesheet");
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onMouseDown: function(event)
+    {
+        //console.log("onMouseDown", event.target || event.srcElement, event);
+
+        // xxxpedro adjusting coordinates because the panel isn't a window yet
+        var offset = event.clientX - this.panelNode.parentNode.offsetLeft;
+
+        // XXjoe Hack to only allow clicking on the checkbox
+        if (!isLeftClick(event) || offset > 20)
+            return;
+
+        var target = event.target || event.srcElement;
+        if (hasClass(target, "textEditor"))
+            return;
+
+        var row = getAncestorByClass(target, "cssProp");
+        if (row && hasClass(row, "editGroup"))
+        {
+            this.disablePropertyRow(row);
+            cancelEvent(event);
+        }
+    },
+
+    onDoubleClick: function(event)
+    {
+        //console.log("onDoubleClick", event.target || event.srcElement, event);
+
+        // xxxpedro adjusting coordinates because the panel isn't a window yet
+        var offset = event.clientX - this.panelNode.parentNode.offsetLeft;
+
+        if (!isLeftClick(event) || offset <= 20)
+            return;
+
+        var target = event.target || event.srcElement;
+
+        //console.log("ok", target, hasClass(target, "textEditorInner"), !isLeftClick(event), offset <= 20);
+
+        // if the inline editor was clicked, don't insert a new rule
+        if (hasClass(target, "textEditorInner"))
+            return;
+
+        var row = getAncestorByClass(target, "cssRule");
+        if (row && !getAncestorByClass(target, "cssPropName")
+            && !getAncestorByClass(target, "cssPropValue"))
+        {
+            this.insertPropertyRow(row);
+            cancelEvent(event);
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    name: "stylesheet",
+    title: "CSS",
+    parentPanel: null,
+    searchable: true,
+    dependents: ["css", "stylesheet", "dom", "domSide", "layout"],
+
+    options:
+    {
+        hasToolButtons: true
+    },
+
+    create: function()
+    {
+        Firebug.Panel.create.apply(this, arguments);
+
+        this.onMouseDown = bind(this.onMouseDown, this);
+        this.onDoubleClick = bind(this.onDoubleClick, this);
+
+        if (this.name == "stylesheet")
+        {
+            this.onChangeSelect = bind(this.onChangeSelect, this);
+
+            var doc = Firebug.browser.document;
+            var selectNode = this.selectNode = createElement("select");
+
+            CssAnalyzer.processAllStyleSheets(doc, function(doc, styleSheet)
+            {
+                var key = StyleSheetCache.key(styleSheet);
+                var fileName = getFileName(styleSheet.href) || getFileName(doc.location.href);
+                var option = createElement("option", {value: key});
+
+                option.appendChild(Firebug.chrome.document.createTextNode(fileName));
+                selectNode.appendChild(option);
+            });
+
+            this.toolButtonsNode.appendChild(selectNode);
+        }
+        /**/
+    },
+
+    onChangeSelect: function(event)
+    {
+        event = event || window.event;
+        var target = event.srcElement || event.currentTarget;
+        var key = target.value;
+        var styleSheet = StyleSheetCache.get(key);
+
+        this.updateLocation(styleSheet);
+    },
+
+    initialize: function()
+    {
+        Firebug.Panel.initialize.apply(this, arguments);
+
+        //if (!domUtils)
+        //{
+        //    try {
+        //        domUtils = CCSV("@mozilla.org/inspector/dom-utils;1", "inIDOMUtils");
+        //    } catch (exc) {
+        //        if (FBTrace.DBG_ERRORS)
+        //            FBTrace.sysout("@mozilla.org/inspector/dom-utils;1 FAILED to load: "+exc, exc);
+        //    }
+        //}
+
+        //TODO: xxxpedro
+        this.context = Firebug.chrome; // TODO: xxxpedro css2
+        this.document = Firebug.chrome.document; // TODO: xxxpedro css2
+
+        this.initializeNode();
+
+        if (this.name == "stylesheet")
+        {
+            var styleSheets = Firebug.browser.document.styleSheets;
+
+            if (styleSheets.length > 0)
+            {
+                addEvent(this.selectNode, "change", this.onChangeSelect);
+
+                this.updateLocation(styleSheets[0]);
+            }
+        }
+
+        //Firebug.SourceBoxPanel.initialize.apply(this, arguments);
+    },
+
+    shutdown: function()
+    {
+        // must destroy the editor when we leave the panel to avoid problems (Issue 2981)
+        Firebug.Editor.stopEditing();
+
+        if (this.name == "stylesheet")
+        {
+            removeEvent(this.selectNode, "change", this.onChangeSelect);
+        }
+
+        this.destroyNode();
+
+        Firebug.Panel.shutdown.apply(this, arguments);
+    },
+
+    destroy: function(state)
+    {
+        //state.scrollTop = this.panelNode.scrollTop ? this.panelNode.scrollTop : this.lastScrollTop;
+
+        //persistObjects(this, state);
+
+        // xxxpedro we are stopping the editor in the shutdown method already
+        //Firebug.Editor.stopEditing();
+        Firebug.Panel.destroy.apply(this, arguments);
+    },
+
+    initializeNode: function(oldPanelNode)
+    {
+        addEvent(this.panelNode, "mousedown", this.onMouseDown);
+        addEvent(this.panelNode, "dblclick", this.onDoubleClick);
+        //Firebug.SourceBoxPanel.initializeNode.apply(this, arguments);
+        //dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'css']);
+    },
+
+    destroyNode: function()
+    {
+        removeEvent(this.panelNode, "mousedown", this.onMouseDown);
+        removeEvent(this.panelNode, "dblclick", this.onDoubleClick);
+        //Firebug.SourceBoxPanel.destroyNode.apply(this, arguments);
+        //dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'css']);
+    },
+
+    ishow: function(state)
+    {
+        Firebug.Inspector.stopInspecting(true);
+
+        this.showToolbarButtons("fbCSSButtons", true);
+
+        if (this.context.loaded && !this.location) // wait for loadedContext to restore the panel
+        {
+            restoreObjects(this, state);
+
+            if (!this.location)
+                this.location = this.getDefaultLocation();
+
+            if (state && state.scrollTop)
+                this.panelNode.scrollTop = state.scrollTop;
+        }
+    },
+
+    ihide: function()
+    {
+        this.showToolbarButtons("fbCSSButtons", false);
+
+        this.lastScrollTop = this.panelNode.scrollTop;
+    },
+
+    supportsObject: function(object)
+    {
+        if (object instanceof CSSStyleSheet)
+            return 1;
+        else if (object instanceof CSSStyleRule)
+            return 2;
+        else if (object instanceof CSSStyleDeclaration)
+            return 2;
+        else if (object instanceof SourceLink && object.type == "css" && reCSS.test(object.href))
+            return 2;
+        else
+            return 0;
+    },
+
+    updateLocation: function(styleSheet)
+    {
+        if (!styleSheet)
+            return;
+        if (styleSheet.editStyleSheet)
+            styleSheet = styleSheet.editStyleSheet.sheet;
+
+        // if it is a restricted stylesheet, show the warning message and abort the update process
+        if (styleSheet.restricted)
+        {
+            FirebugReps.Warning.tag.replace({object: "AccessRestricted"}, this.panelNode);
+
+            // TODO: xxxpedro remove when there the external resource problem is fixed
+            CssAnalyzer.externalStyleSheetWarning.tag.append({
+                object: "The stylesheet could not be loaded due to access restrictions. ",
+                link: "more...",
+                href: "http://getfirebug.com/wiki/index.php/Firebug_Lite_FAQ#I_keep_seeing_.22Access_to_restricted_URI_denied.22"
+            }, this.panelNode);
+
+            return;
+        }
+
+        var rules = this.getStyleSheetRules(this.context, styleSheet);
+
+        var result;
+        if (rules.length)
+            // FIXME xxxpedro chromenew this is making iPad's Safari to crash
+            result = this.template.tag.replace({rules: rules}, this.panelNode);
+        else
+            result = FirebugReps.Warning.tag.replace({object: "EmptyStyleSheet"}, this.panelNode);
+
+        // TODO: xxxpedro need to fix showToolbarButtons function
+        //this.showToolbarButtons("fbCSSButtons", !isSystemStyleSheet(this.location));
+
+        //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, this.panelNode]);
+    },
+
+    updateSelection: function(object)
+    {
+        this.selection = null;
+
+        if (object instanceof CSSStyleDeclaration) {
+            object = object.parentRule;
+        }
+
+        if (object instanceof CSSStyleRule)
+        {
+            this.navigate(object.parentStyleSheet);
+            this.highlightRule(object);
+        }
+        else if (object instanceof CSSStyleSheet)
+        {
+            this.navigate(object);
+        }
+        else if (object instanceof SourceLink)
+        {
+            try
+            {
+                var sourceLink = object;
+
+                var sourceFile = getSourceFileByHref(sourceLink.href, this.context);
+                if (sourceFile)
+                {
+                    clearNode(this.panelNode);  // replace rendered stylesheets
+                    this.showSourceFile(sourceFile);
+
+                    var lineNo = object.line;
+                    if (lineNo)
+                        this.scrollToLine(lineNo, this.jumpHighlightFactory(lineNo, this.context));
+                }
+                else // XXXjjb we should not be taking this path
+                {
+                    var stylesheet = getStyleSheetByHref(sourceLink.href, this.context);
+                    if (stylesheet)
+                        this.navigate(stylesheet);
+                    else
+                    {
+                        if (FBTrace.DBG_CSS)
+                            FBTrace.sysout("css.updateSelection no sourceFile for "+sourceLink.href, sourceLink);
+                    }
+                }
+            }
+            catch(exc) {
+                if (FBTrace.DBG_CSS)
+                    FBTrace.sysout("css.upDateSelection FAILS "+exc, exc);
+            }
+        }
+    },
+
+    updateOption: function(name, value)
+    {
+        if (name == "expandShorthandProps")
+            this.refresh();
+    },
+
+    getLocationList: function()
+    {
+        var styleSheets = getAllStyleSheets(this.context);
+        return styleSheets;
+    },
+
+    getOptionsMenuItems: function()
+    {
+        return [
+            {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps,
+                    command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") },
+            "-",
+            {label: "Refresh", command: bind(this.refresh, this) }
+        ];
+    },
+
+    getContextMenuItems: function(style, target)
+    {
+        var items = [];
+
+        if (this.infoTipType == "color")
+        {
+            items.push(
+                {label: "CopyColor",
+                    command: bindFixed(copyToClipboard, FBL, this.infoTipObject) }
+            );
+        }
+        else if (this.infoTipType == "image")
+        {
+            items.push(
+                {label: "CopyImageLocation",
+                    command: bindFixed(copyToClipboard, FBL, this.infoTipObject) },
+                {label: "OpenImageInNewTab",
+                    command: bindFixed(openNewTab, FBL, this.infoTipObject) }
+            );
+        }
+
+        ///if (this.selection instanceof Element)
+        if (isElement(this.selection))
+        {
+            items.push(
+                //"-",
+                {label: "EditStyle",
+                    command: bindFixed(this.editElementStyle, this) }
+            );
+        }
+        else if (!isSystemStyleSheet(this.selection))
+        {
+            items.push(
+                    //"-",
+                    {label: "NewRule",
+                        command: bindFixed(this.insertRule, this, target) }
+                );
+        }
+
+        var cssRule = getAncestorByClass(target, "cssRule");
+        if (cssRule && hasClass(cssRule, "cssEditableRule"))
+        {
+            items.push(
+                "-",
+                {label: "NewProp",
+                    command: bindFixed(this.insertPropertyRow, this, target) }
+            );
+
+            var propRow = getAncestorByClass(target, "cssProp");
+            if (propRow)
+            {
+                var propName = getChildByClass(propRow, "cssPropName")[textContent];
+                var isDisabled = hasClass(propRow, "disabledStyle");
+
+                items.push(
+                    {label: $STRF("EditProp", [propName]), nol10n: true,
+                        command: bindFixed(this.editPropertyRow, this, propRow) },
+                    {label: $STRF("DeleteProp", [propName]), nol10n: true,
+                        command: bindFixed(this.deletePropertyRow, this, propRow) },
+                    {label: $STRF("DisableProp", [propName]), nol10n: true,
+                        type: "checkbox", checked: isDisabled,
+                        command: bindFixed(this.disablePropertyRow, this, propRow) }
+                );
+            }
+        }
+
+        items.push(
+            "-",
+            {label: "Refresh", command: bind(this.refresh, this) }
+        );
+
+        return items;
+    },
+
+    browseObject: function(object)
+    {
+        if (this.infoTipType == "image")
+        {
+            openNewTab(this.infoTipObject);
+            return true;
+        }
+    },
+
+    showInfoTip: function(infoTip, target, x, y)
+    {
+        var propValue = getAncestorByClass(target, "cssPropValue");
+        if (propValue)
+        {
+            var offset = getClientOffset(propValue);
+            var offsetX = x-offset.x;
+
+            var text = propValue[textContent];
+            var charWidth = propValue.offsetWidth/text.length;
+            var charOffset = Math.floor(offsetX/charWidth);
+
+            var cssValue = parseCSSValue(text, charOffset);
+            if (cssValue)
+            {
+                if (cssValue.value == this.infoTipValue)
+                    return true;
+
+                this.infoTipValue = cssValue.value;
+
+                if (cssValue.type == "rgb" || (!cssValue.type && isColorKeyword(cssValue.value)))
+                {
+                    this.infoTipType = "color";
+                    this.infoTipObject = cssValue.value;
+
+                    return Firebug.InfoTip.populateColorInfoTip(infoTip, cssValue.value);
+                }
+                else if (cssValue.type == "url")
+                {
+                    ///var propNameNode = target.parentNode.getElementsByClassName("cssPropName").item(0);
+                    var propNameNode = getElementByClass(target.parentNode, "cssPropName");
+                    if (propNameNode && isImageRule(propNameNode[textContent]))
+                    {
+                        var rule = Firebug.getRepObject(target);
+                        var baseURL = this.getStylesheetURL(rule);
+                        var relURL = parseURLValue(cssValue.value);
+                        var absURL = isDataURL(relURL) ? relURL:absoluteURL(relURL, baseURL);
+                        var repeat = parseRepeatValue(text);
+
+                        this.infoTipType = "image";
+                        this.infoTipObject = absURL;
+
+                        return Firebug.InfoTip.populateImageInfoTip(infoTip, absURL, repeat);
+                    }
+                }
+            }
+        }
+
+        delete this.infoTipType;
+        delete this.infoTipValue;
+        delete this.infoTipObject;
+    },
+
+    getEditor: function(target, value)
+    {
+        if (target == this.panelNode
+            || hasClass(target, "cssSelector") || hasClass(target, "cssRule")
+            || hasClass(target, "cssSheet"))
+        {
+            if (!this.ruleEditor)
+                this.ruleEditor = new CSSRuleEditor(this.document);
+
+            return this.ruleEditor;
+        }
+        else
+        {
+            if (!this.editor)
+                this.editor = new CSSEditor(this.document);
+
+            return this.editor;
+        }
+    },
+
+    getDefaultLocation: function()
+    {
+        try
+        {
+            var styleSheets = this.context.window.document.styleSheets;
+            if (styleSheets.length)
+            {
+                var sheet = styleSheets[0];
+                return (Firebug.filterSystemURLs && isSystemURL(getURLForStyleSheet(sheet))) ? null : sheet;
+            }
+        }
+        catch (exc)
+        {
+            if (FBTrace.DBG_LOCATIONS)
+                FBTrace.sysout("css.getDefaultLocation FAILS "+exc, exc);
+        }
+    },
+
+    getObjectDescription: function(styleSheet)
+    {
+        var url = getURLForStyleSheet(styleSheet);
+        var instance = getInstanceForStyleSheet(styleSheet);
+
+        var baseDescription = splitURLBase(url);
+        if (instance) {
+          baseDescription.name = baseDescription.name + " #" + (instance + 1);
+        }
+        return baseDescription;
+    },
+
+    search: function(text, reverse)
+    {
+        var curDoc = this.searchCurrentDoc(!Firebug.searchGlobal, text, reverse);
+        if (!curDoc && Firebug.searchGlobal)
+        {
+            return this.searchOtherDocs(text, reverse);
+        }
+        return curDoc;
+    },
+
+    searchOtherDocs: function(text, reverse)
+    {
+        var scanRE = Firebug.Search.getTestingRegex(text);
+        function scanDoc(styleSheet) {
+            // we don't care about reverse here as we are just looking for existence,
+            // if we do have a result we will handle the reverse logic on display
+            for (var i = 0; i < styleSheet.cssRules.length; i++)
+            {
+                if (scanRE.test(styleSheet.cssRules[i].cssText))
+                {
+                    return true;
+                }
+            }
+        }
+
+        if (this.navigateToNextDocument(scanDoc, reverse))
+        {
+            return this.searchCurrentDoc(true, text, reverse);
+        }
+    },
+
+    searchCurrentDoc: function(wrapSearch, text, reverse)
+    {
+        if (!text)
+        {
+            delete this.currentSearch;
+            return false;
+        }
+
+        var row;
+        if (this.currentSearch && text == this.currentSearch.text)
+        {
+            row = this.currentSearch.findNext(wrapSearch, false, reverse, Firebug.Search.isCaseSensitive(text));
+        }
+        else
+        {
+            if (this.editing)
+            {
+                this.currentSearch = new TextSearch(this.stylesheetEditor.box);
+                row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
+
+                if (row)
+                {
+                    var sel = this.document.defaultView.getSelection();
+                    sel.removeAllRanges();
+                    sel.addRange(this.currentSearch.range);
+                    scrollSelectionIntoView(this);
+                    return true;
+                }
+                else
+                    return false;
+            }
+            else
+            {
+                function findRow(node) { return node.nodeType == 1 ? node : node.parentNode; }
+                this.currentSearch = new TextSearch(this.panelNode, findRow);
+                row = this.currentSearch.find(text, reverse, Firebug.Search.isCaseSensitive(text));
+            }
+        }
+
+        if (row)
+        {
+            this.document.defaultView.getSelection().selectAllChildren(row);
+            scrollIntoCenterView(row, this.panelNode);
+            dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, row]);
+            return true;
+        }
+        else
+        {
+            dispatch([Firebug.A11yModel], 'onCSSSearchMatchFound', [this, text, null]);
+            return false;
+        }
+    },
+
+    getSearchOptionsMenuItems: function()
+    {
+        return [
+            Firebug.Search.searchOptionMenu("search.Case_Sensitive", "searchCaseSensitive"),
+            Firebug.Search.searchOptionMenu("search.Multiple_Files", "searchGlobal")
+        ];
+    }
+});
+/**/
+// ************************************************************************************************
+
+function CSSElementPanel() {}
+
+CSSElementPanel.prototype = extend(Firebug.CSSStyleSheetPanel.prototype,
+{
+    template: domplate(
+    {
+        cascadedTag:
+            DIV({"class": "a11yCSSView",  role : 'presentation'},
+                DIV({role : 'list', 'aria-label' : $STR('aria.labels.style rules') },
+                    FOR("rule", "$rules",
+                        TAG("$ruleTag", {rule: "$rule"})
+                    )
+                ),
+                DIV({role : "list", 'aria-label' :$STR('aria.labels.inherited style rules')},
+                    FOR("section", "$inherited",
+                        H1({"class": "cssInheritHeader groupHeader focusRow", role : 'listitem' },
+                            SPAN({"class": "cssInheritLabel"}, "$inheritLabel"),
+                            TAG(FirebugReps.Element.shortTag, {object: "$section.element"})
+                        ),
+                        DIV({role : 'group'},
+                            FOR("rule", "$section.rules",
+                                TAG("$ruleTag", {rule: "$rule"})
+                            )
+                        )
+                    )
+                 )
+            ),
+
+        ruleTag:
+            isIE ?
+            // IE needs the sourceLink first, otherwise it will be rendered outside the panel
+            DIV({"class": "cssElementRuleContainer"},
+                TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"}),
+                TAG(CSSStyleRuleTag.tag, {rule: "$rule"})
+            )
+            :
+            // other browsers need the sourceLink last, otherwise it will cause an extra space
+            // before the rule representation
+            DIV({"class": "cssElementRuleContainer"},
+                TAG(CSSStyleRuleTag.tag, {rule: "$rule"}),
+                TAG(FirebugReps.SourceLink.tag, {object: "$rule.sourceLink"})
+            )
+    }),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    updateCascadeView: function(element)
+    {
+        //dispatch([Firebug.A11yModel], 'onBeforeCSSRulesAdded', [this]);
+        var rules = [], sections = [], usedProps = {};
+        this.getInheritedRules(element, sections, usedProps);
+        this.getElementRules(element, rules, usedProps);
+
+        if (rules.length || sections.length)
+        {
+            var inheritLabel = "Inherited from"; // $STR("InheritedFrom");
+            var result = this.template.cascadedTag.replace({rules: rules, inherited: sections,
+                inheritLabel: inheritLabel}, this.panelNode);
+            //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]);
+        }
+        else
+        {
+            var result = FirebugReps.Warning.tag.replace({object: "EmptyElementCSS"}, this.panelNode);
+            //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]);
+        }
+
+        // TODO: xxxpedro remove when there the external resource problem is fixed
+        if (CssAnalyzer.hasExternalStyleSheet())
+            CssAnalyzer.externalStyleSheetWarning.tag.append({
+                object: "The results here may be inaccurate because some " +
+                        "stylesheets could not be loaded due to access restrictions. ",
+                link: "more...",
+                href: "http://getfirebug.com/wiki/index.php/Firebug_Lite_FAQ#I_keep_seeing_.22This_element_has_no_style_rules.22"
+            }, this.panelNode);
+    },
+
+    getStylesheetURL: function(rule)
+    {
+        // if the parentStyleSheet.href is null, CSS std says its inline style.
+        // TODO: xxxpedro IE doesn't have rule.parentStyleSheet so we must fall back to the doc.location
+        if (rule && rule.parentStyleSheet && rule.parentStyleSheet.href)
+            return rule.parentStyleSheet.href;
+        else
+            return this.selection.ownerDocument.location.href;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getInheritedRules: function(element, sections, usedProps)
+    {
+        var parent = element.parentNode;
+        if (parent && parent.nodeType == 1)
+        {
+            this.getInheritedRules(parent, sections, usedProps);
+
+            var rules = [];
+            this.getElementRules(parent, rules, usedProps, true);
+
+            if (rules.length)
+                sections.splice(0, 0, {element: parent, rules: rules});
+        }
+    },
+
+    getElementRules: function(element, rules, usedProps, inheritMode)
+    {
+        var inspectedRules, displayedRules = {};
+
+        inspectedRules = CssAnalyzer.getElementCSSRules(element);
+
+        if (inspectedRules)
+        {
+            for (var i = 0, length=inspectedRules.length; i < length; ++i)
+            {
+                var ruleId = inspectedRules[i];
+                var ruleData = CssAnalyzer.getRuleData(ruleId);
+                var rule = ruleData.rule;
+
+                var ssid = ruleData.styleSheetId;
+                var parentStyleSheet = StyleSheetCache.get(ssid);
+
+                var href = parentStyleSheet.externalURL ? parentStyleSheet.externalURL : parentStyleSheet.href;  // Null means inline
+
+                var instance = null;
+                //var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument);
+
+                var isSystemSheet = false;
+                //var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet);
+
+                if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules
+                    continue;
+
+                if (!href)
+                    href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452
+
+                var props = this.getRuleProperties(this.context, rule, inheritMode);
+                if (inheritMode && !props.length)
+                    continue;
+
+                //
+                //var line = domUtils.getRuleLine(rule);
+                // TODO: xxxpedro CSS line number
+                var line = ruleData.lineNo;
+
+                var ruleId = rule.selectorText+"/"+line;
+                var sourceLink = new SourceLink(href, line, "css", rule, instance);
+
+                this.markOverridenProps(props, usedProps, inheritMode);
+
+                rules.splice(0, 0, {rule: rule, id: ruleId,
+                        selector: ruleData.selector, sourceLink: sourceLink,
+                        props: props, inherited: inheritMode,
+                        isSystemSheet: isSystemSheet});
+            }
+        }
+
+        if (element.style)
+            this.getStyleProperties(element, rules, usedProps, inheritMode);
+
+        if (FBTrace.DBG_CSS)
+            FBTrace.sysout("getElementRules "+rules.length+" rules for "+getElementXPath(element), rules);
+    },
+    /*
+    getElementRules: function(element, rules, usedProps, inheritMode)
+    {
+        var inspectedRules, displayedRules = {};
+        try
+        {
+            inspectedRules = domUtils ? domUtils.getCSSStyleRules(element) : null;
+        } catch (exc) {}
+
+        if (inspectedRules)
+        {
+            for (var i = 0; i < inspectedRules.Count(); ++i)
+            {
+                var rule = QI(inspectedRules.GetElementAt(i), nsIDOMCSSStyleRule);
+
+                var href = rule.parentStyleSheet.href;  // Null means inline
+
+                var instance = getInstanceForStyleSheet(rule.parentStyleSheet, element.ownerDocument);
+
+                var isSystemSheet = isSystemStyleSheet(rule.parentStyleSheet);
+                if (!Firebug.showUserAgentCSS && isSystemSheet) // This removes user agent rules
+                    continue;
+                if (!href)
+                    href = element.ownerDocument.location.href; // http://code.google.com/p/fbug/issues/detail?id=452
+
+                var props = this.getRuleProperties(this.context, rule, inheritMode);
+                if (inheritMode && !props.length)
+                    continue;
+
+                var line = domUtils.getRuleLine(rule);
+                var ruleId = rule.selectorText+"/"+line;
+                var sourceLink = new SourceLink(href, line, "css", rule, instance);
+
+                this.markOverridenProps(props, usedProps, inheritMode);
+
+                rules.splice(0, 0, {rule: rule, id: ruleId,
+                        selector: rule.selectorText, sourceLink: sourceLink,
+                        props: props, inherited: inheritMode,
+                        isSystemSheet: isSystemSheet});
+            }
+        }
+
+        if (element.style)
+            this.getStyleProperties(element, rules, usedProps, inheritMode);
+
+        if (FBTrace.DBG_CSS)
+            FBTrace.sysout("getElementRules "+rules.length+" rules for "+getElementXPath(element), rules);
+    },
+    /**/
+    markOverridenProps: function(props, usedProps, inheritMode)
+    {
+        for (var i = 0; i < props.length; ++i)
+        {
+            var prop = props[i];
+            if ( usedProps.hasOwnProperty(prop.name) )
+            {
+                var deadProps = usedProps[prop.name]; // all previous occurrences of this property
+                for (var j = 0; j < deadProps.length; ++j)
+                {
+                    var deadProp = deadProps[j];
+                    if (!deadProp.disabled && !deadProp.wasInherited && deadProp.important && !prop.important)
+                        prop.overridden = true;  // new occurrence overridden
+                    else if (!prop.disabled)
+                        deadProp.overridden = true;  // previous occurrences overridden
+                }
+            }
+            else
+                usedProps[prop.name] = [];
+
+            prop.wasInherited = inheritMode ? true : false;
+            usedProps[prop.name].push(prop);  // all occurrences of a property seen so far, by name
+        }
+    },
+
+    getStyleProperties: function(element, rules, usedProps, inheritMode)
+    {
+        var props = this.parseCSSProps(element.style, inheritMode);
+        this.addOldProperties(this.context, getElementXPath(element), inheritMode, props);
+
+        sortProperties(props);
+        this.markOverridenProps(props, usedProps, inheritMode);
+
+        if (props.length)
+            rules.splice(0, 0,
+                    {rule: element, id: getElementXPath(element),
+                        selector: "element.style", props: props, inherited: inheritMode});
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    name: "css",
+    title: "Style",
+    parentPanel: "HTML",
+    order: 0,
+
+    initialize: function()
+    {
+        this.context = Firebug.chrome; // TODO: xxxpedro css2
+        this.document = Firebug.chrome.document; // TODO: xxxpedro css2
+
+        Firebug.CSSStyleSheetPanel.prototype.initialize.apply(this, arguments);
+
+        // TODO: xxxpedro css2
+        var selection = ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId);
+        if (selection)
+            this.select(selection, true);
+
+        //this.updateCascadeView(document.getElementsByTagName("h1")[0]);
+        //this.updateCascadeView(document.getElementById("build"));
+
+        /*
+        this.onStateChange = bindFixed(this.contentStateCheck, this);
+        this.onHoverChange = bindFixed(this.contentStateCheck, this, STATE_HOVER);
+        this.onActiveChange = bindFixed(this.contentStateCheck, this, STATE_ACTIVE);
+        /**/
+    },
+
+    ishow: function(state)
+    {
+    },
+
+    watchWindow: function(win)
+    {
+        if (domUtils)
+        {
+            // Normally these would not be required, but in order to update after the state is set
+            // using the options menu we need to monitor these global events as well
+            var doc = win.document;
+            ///addEvent(doc, "mouseover", this.onHoverChange);
+            ///addEvent(doc, "mousedown", this.onActiveChange);
+        }
+    },
+    unwatchWindow: function(win)
+    {
+        var doc = win.document;
+        ///removeEvent(doc, "mouseover", this.onHoverChange);
+        ///removeEvent(doc, "mousedown", this.onActiveChange);
+
+        if (isAncestor(this.stateChangeEl, doc))
+        {
+            this.removeStateChangeHandlers();
+        }
+    },
+
+    supportsObject: function(object)
+    {
+        return object instanceof Element ? 1 : 0;
+    },
+
+    updateView: function(element)
+    {
+        this.updateCascadeView(element);
+        if (domUtils)
+        {
+            this.contentState = safeGetContentState(element);
+            this.addStateChangeHandlers(element);
+        }
+    },
+
+    updateSelection: function(element)
+    {
+        if ( !instanceOf(element , "Element") ) // html supports SourceLink
+            return;
+
+        if (sothinkInstalled)
+        {
+            FirebugReps.Warning.tag.replace({object: "SothinkWarning"}, this.panelNode);
+            return;
+        }
+
+        /*
+        if (!domUtils)
+        {
+            FirebugReps.Warning.tag.replace({object: "DOMInspectorWarning"}, this.panelNode);
+            return;
+        }
+        /**/
+
+        if (!element)
+            return;
+
+        this.updateView(element);
+    },
+
+    updateOption: function(name, value)
+    {
+        if (name == "showUserAgentCSS" || name == "expandShorthandProps")
+            this.refresh();
+    },
+
+    getOptionsMenuItems: function()
+    {
+        var ret = [
+            {label: "Show User Agent CSS", type: "checkbox", checked: Firebug.showUserAgentCSS,
+                    command: bindFixed(Firebug.togglePref, Firebug, "showUserAgentCSS") },
+            {label: "Expand Shorthand Properties", type: "checkbox", checked: Firebug.expandShorthandProps,
+                    command: bindFixed(Firebug.togglePref, Firebug, "expandShorthandProps") }
+        ];
+        if (domUtils && this.selection)
+        {
+            var state = safeGetContentState(this.selection);
+
+            ret.push("-");
+            ret.push({label: ":active", type: "checkbox", checked: state & STATE_ACTIVE,
+              command: bindFixed(this.updateContentState, this, STATE_ACTIVE, state & STATE_ACTIVE)});
+            ret.push({label: ":hover", type: "checkbox", checked: state & STATE_HOVER,
+              command: bindFixed(this.updateContentState, this, STATE_HOVER, state & STATE_HOVER)});
+        }
+        return ret;
+    },
+
+    updateContentState: function(state, remove)
+    {
+        domUtils.setContentState(remove ? this.selection.ownerDocument.documentElement : this.selection, state);
+        this.refresh();
+    },
+
+    addStateChangeHandlers: function(el)
+    {
+      this.removeStateChangeHandlers();
+
+      /*
+      addEvent(el, "focus", this.onStateChange);
+      addEvent(el, "blur", this.onStateChange);
+      addEvent(el, "mouseup", this.onStateChange);
+      addEvent(el, "mousedown", this.onStateChange);
+      addEvent(el, "mouseover", this.onStateChange);
+      addEvent(el, "mouseout", this.onStateChange);
+      /**/
+
+      this.stateChangeEl = el;
+    },
+
+    removeStateChangeHandlers: function()
+    {
+        var sel = this.stateChangeEl;
+        if (sel)
+        {
+            /*
+            removeEvent(sel, "focus", this.onStateChange);
+            removeEvent(sel, "blur", this.onStateChange);
+            removeEvent(sel, "mouseup", this.onStateChange);
+            removeEvent(sel, "mousedown", this.onStateChange);
+            removeEvent(sel, "mouseover", this.onStateChange);
+            removeEvent(sel, "mouseout", this.onStateChange);
+            /**/
+        }
+    },
+
+    contentStateCheck: function(state)
+    {
+        if (!state || this.contentState & state)
+        {
+            var timeoutRunner = bindFixed(function()
+            {
+                var newState = safeGetContentState(this.selection);
+                if (newState != this.contentState)
+                {
+                    this.context.invalidatePanels(this.name);
+                }
+            }, this);
+
+            // Delay exec until after the event has processed and the state has been updated
+            setTimeout(timeoutRunner, 0);
+        }
+    }
+});
+
+function safeGetContentState(selection)
+{
+    try
+    {
+        return domUtils.getContentState(selection);
+    }
+    catch (e)
+    {
+        if (FBTrace.DBG_ERRORS)
+            FBTrace.sysout("css.safeGetContentState; EXCEPTION", e);
+    }
+}
+
+// ************************************************************************************************
+
+function CSSComputedElementPanel() {}
+
+CSSComputedElementPanel.prototype = extend(CSSElementPanel.prototype,
+{
+    template: domplate(
+    {
+        computedTag:
+            DIV({"class": "a11yCSSView", role : "list", "aria-label" : $STR('aria.labels.computed styles')},
+                FOR("group", "$groups",
+                    H1({"class": "cssInheritHeader groupHeader focusRow", role : "listitem"},
+                        SPAN({"class": "cssInheritLabel"}, "$group.title")
+                    ),
+                    TABLE({width: "100%", role : 'group'},
+                        TBODY({role : 'presentation'},
+                            FOR("prop", "$group.props",
+                                TR({"class": 'focusRow computedStyleRow', role : 'listitem'},
+                                    TD({"class": "stylePropName", role : 'presentation'}, "$prop.name"),
+                                    TD({"class": "stylePropValue", role : 'presentation'}, "$prop.value")
+                                )
+                            )
+                        )
+                    )
+                )
+            )
+    }),
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    updateComputedView: function(element)
+    {
+        var win = isIE ?
+                element.ownerDocument.parentWindow :
+                element.ownerDocument.defaultView;
+
+        var style = isIE ?
+                element.currentStyle :
+                win.getComputedStyle(element, "");
+
+        var groups = [];
+
+        for (var groupName in styleGroups)
+        {
+            // TODO: xxxpedro i18n $STR
+            //var title = $STR("StyleGroup-" + groupName);
+            var title = styleGroupTitles[groupName];
+            var group = {title: title, props: []};
+            groups.push(group);
+
+            var props = styleGroups[groupName];
+            for (var i = 0; i < props.length; ++i)
+            {
+                var propName = props[i];
+                var propValue = style.getPropertyValue ?
+                        style.getPropertyValue(propName) :
+                        ""+style[toCamelCase(propName)];
+
+                if (propValue === undefined || propValue === null)
+                    continue;
+
+                propValue = stripUnits(rgbToHex(propValue));
+                if (propValue)
+                    group.props.push({name: propName, value: propValue});
+            }
+        }
+
+        var result = this.template.computedTag.replace({groups: groups}, this.panelNode);
+        //dispatch([Firebug.A11yModel], 'onCSSRulesAdded', [this, result]);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    name: "computed",
+    title: "Computed",
+    parentPanel: "HTML",
+    order: 1,
+
+    updateView: function(element)
+    {
+        this.updateComputedView(element);
+    },
+
+    getOptionsMenuItems: function()
+    {
+        return [
+            {label: "Refresh", command: bind(this.refresh, this) }
+        ];
+    }
+});
+
+// ************************************************************************************************
+// CSSEditor
+
+function CSSEditor(doc)
+{
+    this.initializeInline(doc);
+}
+
+CSSEditor.prototype = domplate(Firebug.InlineEditor.prototype,
+{
+    insertNewRow: function(target, insertWhere)
+    {
+        var rule = Firebug.getRepObject(target);
+        var emptyProp =
+        {
+            // TODO: xxxpedro - uses charCode(255) to force the element being rendered,
+            // allowing webkit to get the correct position of the property name "span",
+            // when inserting a new CSS rule?
+            name: "",
+            value: "",
+            important: ""
+        };
+
+        if (insertWhere == "before")
+            return CSSPropTag.tag.insertBefore({prop: emptyProp, rule: rule}, target);
+        else
+            return CSSPropTag.tag.insertAfter({prop: emptyProp, rule: rule}, target);
+    },
+
+    saveEdit: function(target, value, previousValue)
+    {
+        // We need to check the value first in order to avoid a problem in IE8
+        // See Issue 3038: Empty (null) styles when adding CSS styles in Firebug Lite
+        if (!value) return;
+
+        target.innerHTML = escapeForCss(value);
+
+        var row = getAncestorByClass(target, "cssProp");
+        if (hasClass(row, "disabledStyle"))
+            toggleClass(row, "disabledStyle");
+
+        var rule = Firebug.getRepObject(target);
+
+        if (hasClass(target, "cssPropName"))
+        {
+            if (value && previousValue != value)  // name of property has changed.
+            {
+                var propValue = getChildByClass(row, "cssPropValue")[textContent];
+                var parsedValue = parsePriority(propValue);
+
+                if (propValue && propValue != "undefined") {
+                    if (FBTrace.DBG_CSS)
+                        FBTrace.sysout("CSSEditor.saveEdit : "+previousValue+"->"+value+" = "+propValue+"\n");
+                    if (previousValue)
+                        Firebug.CSSModule.removeProperty(rule, previousValue);
+                    Firebug.CSSModule.setProperty(rule, value, parsedValue.value, parsedValue.priority);
+                }
+            }
+            else if (!value) // name of the property has been deleted, so remove the property.
+                Firebug.CSSModule.removeProperty(rule, previousValue);
+        }
+        else if (getAncestorByClass(target, "cssPropValue"))
+        {
+            var propName = getChildByClass(row, "cssPropName")[textContent];
+            var propValue = getChildByClass(row, "cssPropValue")[textContent];
+
+            if (FBTrace.DBG_CSS)
+            {
+                FBTrace.sysout("CSSEditor.saveEdit propName=propValue: "+propName +" = "+propValue+"\n");
+               // FBTrace.sysout("CSSEditor.saveEdit BEFORE style:",style);
+            }
+
+            if (value && value != "null")
+            {
+                var parsedValue = parsePriority(value);
+                Firebug.CSSModule.setProperty(rule, propName, parsedValue.value, parsedValue.priority);
+            }
+            else if (previousValue && previousValue != "null")
+                Firebug.CSSModule.removeProperty(rule, propName);
+        }
+
+        this.panel.markChange(this.panel.name == "stylesheet");
+    },
+
+    advanceToNext: function(target, charCode)
+    {
+        if (charCode == 58 /*":"*/ && hasClass(target, "cssPropName"))
+            return true;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    getAutoCompleteRange: function(value, offset)
+    {
+        if (hasClass(this.target, "cssPropName"))
+            return {start: 0, end: value.length-1};
+        else
+            return parseCSSValue(value, offset);
+    },
+
+    getAutoCompleteList: function(preExpr, expr, postExpr)
+    {
+        if (hasClass(this.target, "cssPropName"))
+        {
+            return getCSSPropertyNames();
+        }
+        else
+        {
+            var row = getAncestorByClass(this.target, "cssProp");
+            var propName = getChildByClass(row, "cssPropName")[textContent];
+            return getCSSKeywordsByProperty(propName);
+        }
+    }
+});
+
+//************************************************************************************************
+//CSSRuleEditor
+
+function CSSRuleEditor(doc)
+{
+    this.initializeInline(doc);
+    this.completeAsYouType = false;
+}
+CSSRuleEditor.uniquifier = 0;
+CSSRuleEditor.prototype = domplate(Firebug.InlineEditor.prototype,
+{
+    insertNewRow: function(target, insertWhere)
+    {
+         var emptyRule = {
+                 selector: "",
+                 id: "",
+                 props: [],
+                 isSelectorEditable: true
+         };
+
+         if (insertWhere == "before")
+             return CSSStyleRuleTag.tag.insertBefore({rule: emptyRule}, target);
+         else
+             return CSSStyleRuleTag.tag.insertAfter({rule: emptyRule}, target);
+    },
+
+    saveEdit: function(target, value, previousValue)
+    {
+        if (FBTrace.DBG_CSS)
+            FBTrace.sysout("CSSRuleEditor.saveEdit: '" + value + "'  '" + previousValue + "'", target);
+
+        target.innerHTML = escapeForCss(value);
+
+        if (value === previousValue)     return;
+
+        var row = getAncestorByClass(target, "cssRule");
+        var styleSheet = this.panel.location;
+        styleSheet = styleSheet.editStyleSheet ? styleSheet.editStyleSheet.sheet : styleSheet;
+
+        var cssRules = styleSheet.cssRules;
+        var rule = Firebug.getRepObject(target), oldRule = rule;
+        var ruleIndex = cssRules.length;
+        if (rule || Firebug.getRepObject(row.nextSibling))
+        {
+            var searchRule = rule || Firebug.getRepObject(row.nextSibling);
+            for (ruleIndex=0; ruleIndex<cssRules.length && searchRule!=cssRules[ruleIndex]; ruleIndex++) {}
+        }
+
+        // Delete in all cases except for new add
+        // We want to do this before the insert to ease change tracking
+        if (oldRule)
+        {
+            Firebug.CSSModule.deleteRule(styleSheet, ruleIndex);
+        }
+
+        // Firefox does not follow the spec for the update selector text case.
+        // When attempting to update the value, firefox will silently fail.
+        // See https://bugzilla.mozilla.org/show_bug.cgi?id=37468 for the quite
+        // old discussion of this bug.
+        // As a result we need to recreate the style every time the selector
+        // changes.
+        if (value)
+        {
+            var cssText = [ value, "{" ];
+            var props = row.getElementsByClassName("cssProp");
+            for (var i = 0; i < props.length; i++) {
+                var propEl = props[i];
+                if (!hasClass(propEl, "disabledStyle")) {
+                    cssText.push(getChildByClass(propEl, "cssPropName")[textContent]);
+                    cssText.push(":");
+                    cssText.push(getChildByClass(propEl, "cssPropValue")[textContent]);
+                    cssText.push(";");
+                }
+            }
+            cssText.push("}");
+            cssText = cssText.join("");
+
+            try
+            {
+                var insertLoc = Firebug.CSSModule.insertRule(styleSheet, cssText, ruleIndex);
+                rule = cssRules[insertLoc];
+                ruleIndex++;
+            }
+            catch (err)
+            {
+                if (FBTrace.DBG_CSS || FBTrace.DBG_ERRORS)
+                    FBTrace.sysout("CSS Insert Error: "+err, err);
+
+                target.innerHTML = escapeForCss(previousValue);
+                row.repObject = undefined;
+                return;
+            }
+        } else {
+            rule = undefined;
+        }
+
+        // Update the rep object
+        row.repObject = rule;
+        if (!oldRule)
+        {
+            // Who knows what the domutils will return for rule line
+            // for a recently created rule. To be safe we just generate
+            // a unique value as this is only used as an internal key.
+            var ruleId = "new/"+value+"/"+(++CSSRuleEditor.uniquifier);
+            row.setAttribute("ruleId", ruleId);
+        }
+
+        this.panel.markChange(this.panel.name == "stylesheet");
+    }
+});
+
+// ************************************************************************************************
+// StyleSheetEditor
+
+function StyleSheetEditor(doc)
+{
+    this.box = this.tag.replace({}, doc, this);
+    this.input = this.box.firstChild;
+}
+
+StyleSheetEditor.prototype = domplate(Firebug.BaseEditor,
+{
+    multiLine: true,
+
+    tag: DIV(
+        TEXTAREA({"class": "styleSheetEditor fullPanelEditor", oninput: "$onInput"})
+    ),
+
+    getValue: function()
+    {
+        return this.input.value;
+    },
+
+    setValue: function(value)
+    {
+        return this.input.value = value;
+    },
+
+    show: function(target, panel, value, textSize, targetSize)
+    {
+        this.target = target;
+        this.panel = panel;
+
+        this.panel.panelNode.appendChild(this.box);
+
+        this.input.value = value;
+        this.input.focus();
+
+        var command = Firebug.chrome.$("cmd_toggleCSSEditing");
+        command.setAttribute("checked", true);
+    },
+
+    hide: function()
+    {
+        var command = Firebug.chrome.$("cmd_toggleCSSEditing");
+        command.setAttribute("checked", false);
+
+        if (this.box.parentNode == this.panel.panelNode)
+            this.panel.panelNode.removeChild(this.box);
+
+        delete this.target;
+        delete this.panel;
+        delete this.styleSheet;
+    },
+
+    saveEdit: function(target, value, previousValue)
+    {
+        Firebug.CSSModule.freeEdit(this.styleSheet, value);
+    },
+
+    endEditing: function()
+    {
+        this.panel.refresh();
+        return true;
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onInput: function()
+    {
+        Firebug.Editor.update();
+    },
+
+    scrollToLine: function(line, offset)
+    {
+        this.startMeasuring(this.input);
+        var lineHeight = this.measureText().height;
+        this.stopMeasuring();
+
+        this.input.scrollTop = (line * lineHeight) + offset;
+    }
+});
+
+// ************************************************************************************************
+// Local Helpers
+
+var rgbToHex = function rgbToHex(value)
+{
+    return value.replace(/\brgb\((\d{1,3}),\s*(\d{1,3}),\s*(\d{1,3})\)/gi, rgbToHexReplacer);
+};
+
+var rgbToHexReplacer = function(_, r, g, b) {
+    return '#' + ((1 << 24) + (r << 16) + (g << 8) + (b << 0)).toString(16).substr(-6).toUpperCase();
+};
+
+var stripUnits = function stripUnits(value)
+{
+    // remove units from '0px', '0em' etc. leave non-zero units in-tact.
+    return value.replace(/(url\(.*?\)|[^0]\S*\s*)|0(%|em|ex|px|in|cm|mm|pt|pc)(\s|$)/gi, stripUnitsReplacer);
+};
+
+var stripUnitsReplacer = function(_, skip, remove, whitespace) {
+    return skip || ('0' + whitespace);
+};
+
+function parsePriority(value)
+{
+    var rePriority = /(.*?)\s*(!important)?$/;
+    var m = rePriority.exec(value);
+    var propValue = m ? m[1] : "";
+    var priority = m && m[2] ? "important" : "";
+    return {value: propValue, priority: priority};
+}
+
+function parseURLValue(value)
+{
+    var m = reURL.exec(value);
+    return m ? m[1] : "";
+}
+
+function parseRepeatValue(value)
+{
+    var m = reRepeat.exec(value);
+    return m ? m[0] : "";
+}
+
+function parseCSSValue(value, offset)
+{
+    var start = 0;
+    var m;
+    while (1)
+    {
+        m = reSplitCSS.exec(value);
+        if (m && m.index+m[0].length < offset)
+        {
+            value = value.substr(m.index+m[0].length);
+            start += m.index+m[0].length;
+            offset -= m.index+m[0].length;
+        }
+        else
+            break;
+    }
+
+    if (m)
+    {
+        var type;
+        if (m[1])
+            type = "url";
+        else if (m[2] || m[3])
+            type = "rgb";
+        else if (m[4])
+            type = "int";
+
+        return {value: m[0], start: start+m.index, end: start+m.index+(m[0].length-1), type: type};
+    }
+}
+
+function findPropByName(props, name)
+{
+    for (var i = 0; i < props.length; ++i)
+    {
+        if (props[i].name == name)
+            return i;
+    }
+}
+
+function sortProperties(props)
+{
+    props.sort(function(a, b)
+    {
+        return a.name > b.name ? 1 : -1;
+    });
+}
+
+function getTopmostRuleLine(panelNode)
+{
+    for (var child = panelNode.firstChild; child; child = child.nextSibling)
+    {
+        if (child.offsetTop+child.offsetHeight > panelNode.scrollTop)
+        {
+            var rule = child.repObject;
+            if (rule)
+                return {
+                    line: domUtils.getRuleLine(rule),
+                    offset: panelNode.scrollTop-child.offsetTop
+                };
+        }
+    }
+    return 0;
+}
+
+function getStyleSheetCSS(sheet, context)
+{
+    if (sheet.ownerNode instanceof HTMLStyleElement)
+        return sheet.ownerNode.innerHTML;
+    else
+        return context.sourceCache.load(sheet.href).join("");
+}
+
+function getStyleSheetOwnerNode(sheet) {
+    for (; sheet && !sheet.ownerNode; sheet = sheet.parentStyleSheet);
+
+    return sheet.ownerNode;
+}
+
+function scrollSelectionIntoView(panel)
+{
+    var selCon = getSelectionController(panel);
+    selCon.scrollSelectionIntoView(
+            nsISelectionController.SELECTION_NORMAL,
+            nsISelectionController.SELECTION_FOCUS_REGION, true);
+}
+
+function getSelectionController(panel)
+{
+    var browser = Firebug.chrome.getPanelBrowser(panel);
+    return browser.docShell.QueryInterface(nsIInterfaceRequestor)
+        .getInterface(nsISelectionDisplay)
+        .QueryInterface(nsISelectionController);
+}
+
+// ************************************************************************************************
+
+Firebug.registerModule(Firebug.CSSModule);
+Firebug.registerPanel(Firebug.CSSStyleSheetPanel);
+Firebug.registerPanel(CSSElementPanel);
+Firebug.registerPanel(CSSComputedElementPanel);
+
+// ************************************************************************************************
+
+}});
+
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Script Module
+
+Firebug.Script = extend(Firebug.Module,
+{
+    getPanel: function()
+    {
+        return Firebug.chrome ? Firebug.chrome.getPanel("Script") : null;
+    },
+
+    selectSourceCode: function(index)
+    {
+        this.getPanel().selectSourceCode(index);
+    }
+});
+
+Firebug.registerModule(Firebug.Script);
+
+
+// ************************************************************************************************
+// Script Panel
+
+function ScriptPanel(){};
+
+ScriptPanel.prototype = extend(Firebug.Panel,
+{
+    name: "Script",
+    title: "Script",
+
+    selectIndex: 0, // index of the current selectNode's option
+    sourceIndex: -1, // index of the script node, based in doc.getElementsByTagName("script")
+
+    options: {
+        hasToolButtons: true
+    },
+
+    create: function()
+    {
+        Firebug.Panel.create.apply(this, arguments);
+
+        this.onChangeSelect = bind(this.onChangeSelect, this);
+
+        var doc = Firebug.browser.document;
+        var scripts = doc.getElementsByTagName("script");
+        var selectNode = this.selectNode = createElement("select");
+
+        for(var i=0, script; script=scripts[i]; i++)
+        {
+            // Don't show Firebug Lite source code in the list of options
+            if (Firebug.ignoreFirebugElements && script.getAttribute("firebugIgnore"))
+                continue;
+
+            var fileName = getFileName(script.src) || getFileName(doc.location.href);
+            var option = createElement("option", {value:i});
+
+            option.appendChild(Firebug.chrome.document.createTextNode(fileName));
+            selectNode.appendChild(option);
+        };
+
+        this.toolButtonsNode.appendChild(selectNode);
+    },
+
+    initialize: function()
+    {
+        // we must render the code first, so the persistent state can be restore
+        this.selectSourceCode(this.selectIndex);
+
+        Firebug.Panel.initialize.apply(this, arguments);
+
+        addEvent(this.selectNode, "change", this.onChangeSelect);
+    },
+
+    shutdown: function()
+    {
+        removeEvent(this.selectNode, "change", this.onChangeSelect);
+
+        Firebug.Panel.shutdown.apply(this, arguments);
+    },
+
+    detach: function(oldChrome, newChrome)
+    {
+        Firebug.Panel.detach.apply(this, arguments);
+
+        var oldPanel = oldChrome.getPanel("Script");
+        var index = oldPanel.selectIndex;
+
+        this.selectNode.selectedIndex = index;
+        this.selectIndex = index;
+        this.sourceIndex = -1;
+    },
+
+    onChangeSelect: function(event)
+    {
+        var select = this.selectNode;
+
+        this.selectIndex = select.selectedIndex;
+
+        var option = select.options[select.selectedIndex];
+        if (!option)
+            return;
+
+        var selectedSourceIndex = parseInt(option.value);
+
+        this.renderSourceCode(selectedSourceIndex);
+    },
+
+    selectSourceCode: function(index)
+    {
+        var select = this.selectNode;
+        select.selectedIndex = index;
+
+        var option = select.options[index];
+        if (!option)
+            return;
+
+        var selectedSourceIndex = parseInt(option.value);
+
+        this.renderSourceCode(selectedSourceIndex);
+    },
+
+    renderSourceCode: function(index)
+    {
+        if (this.sourceIndex != index)
+        {
+            var renderProcess = function renderProcess(src)
+            {
+                var html = [],
+                    hl = 0;
+
+                src = isIE && !isExternal ?
+                        src+'\n' :  // IE put an extra line when reading source of local resources
+                        '\n'+src;
+
+                // find the number of lines of code
+                src = src.replace(/\n\r|\r\n/g, "\n");
+                var match = src.match(/[\n]/g);
+                var lines=match ? match.length : 0;
+
+                // render the full source code + line numbers html
+                html[hl++] = '<div><div class="sourceBox" style="left:';
+                html[hl++] = 35 + 7*(lines+'').length;
+                html[hl++] = 'px;"><pre class="sourceCode">';
+                html[hl++] = escapeHTML(src);
+                html[hl++] = '</pre></div><div class="lineNo">';
+
+                // render the line number divs
+                for(var l=1, lines; l<=lines; l++)
+                {
+                    html[hl++] = '<div line="';
+                    html[hl++] = l;
+                    html[hl++] = '">';
+                    html[hl++] = l;
+                    html[hl++] = '</div>';
+                }
+
+                html[hl++] = '</div></div>';
+
+                updatePanel(html);
+            };
+
+            var updatePanel = function(html)
+            {
+                self.panelNode.innerHTML = html.join("");
+
+                // IE needs this timeout, otherwise the panel won't scroll
+                setTimeout(function(){
+                    self.synchronizeUI();
+                },0);
+            };
+
+            var onFailure = function()
+            {
+                FirebugReps.Warning.tag.replace({object: "AccessRestricted"}, self.panelNode);
+            };
+
+            var self = this;
+
+            var doc = Firebug.browser.document;
+            var script = doc.getElementsByTagName("script")[index];
+            var url = getScriptURL(script);
+            var isExternal = url && url != doc.location.href;
+
+            try
+            {
+                if (Firebug.disableResourceFetching)
+                {
+                    renderProcess(Firebug.Lite.Proxy.fetchResourceDisabledMessage);
+                }
+                else if (isExternal)
+                {
+                    Ajax.request({url: url, onSuccess: renderProcess, onFailure: onFailure});
+                }
+                else
+                {
+                    var src = script.innerHTML;
+                    renderProcess(src);
+                }
+            }
+            catch(e)
+            {
+                onFailure();
+            }
+
+            this.sourceIndex = index;
+        }
+    }
+});
+
+Firebug.registerPanel(ScriptPanel);
+
+
+// ************************************************************************************************
+
+
+var getScriptURL = function getScriptURL(script)
+{
+    var reFile = /([^\/\?#]+)(#.+)?$/;
+    var rePath = /^(.*\/)/;
+    var reProtocol = /^\w+:\/\//;
+    var path = null;
+    var doc = Firebug.browser.document;
+
+    var file = reFile.exec(script.src);
+
+    if (file)
+    {
+        var fileName = file[1];
+        var fileOptions = file[2];
+
+        // absolute path
+        if (reProtocol.test(script.src)) {
+            path = rePath.exec(script.src)[1];
+
+        }
+        // relative path
+        else
+        {
+            var r = rePath.exec(script.src);
+            var src = r ? r[1] : script.src;
+            var backDir = /^((?:\.\.\/)+)(.*)/.exec(src);
+            var reLastDir = /^(.*\/)[^\/]+\/$/;
+            path = rePath.exec(doc.location.href)[1];
+
+            // "../some/path"
+            if (backDir)
+            {
+                var j = backDir[1].length/3;
+                var p;
+                while (j-- > 0)
+                    path = reLastDir.exec(path)[1];
+
+                path += backDir[2];
+            }
+
+            else if(src.indexOf("/") != -1)
+            {
+                // "./some/path"
+                if(/^\.\/./.test(src))
+                {
+                    path += src.substring(2);
+                }
+                // "/some/path"
+                else if(/^\/./.test(src))
+                {
+                    var domain = /^(\w+:\/\/[^\/]+)/.exec(path);
+                    path = domain[1] + src;
+                }
+                // "some/path"
+                else
+                {
+                    path += src;
+                }
+            }
+        }
+    }
+
+    var m = path && path.match(/([^\/]+)\/$/) || null;
+
+    if (path && m)
+    {
+        return path + fileName;
+    }
+};
+
+var getFileName = function getFileName(path)
+{
+    if (!path) return "";
+
+    var match = path && path.match(/[^\/]+(\?.*)?(#.*)?$/);
+
+    return match && match[0] || path;
+};
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var ElementCache = Firebug.Lite.Cache.Element;
+
+var insertSliceSize = 18;
+var insertInterval = 40;
+
+var ignoreVars =
+{
+    "__firebug__": 1,
+    "eval": 1,
+
+    // We are forced to ignore Java-related variables, because
+    // trying to access them causes browser freeze
+    "java": 1,
+    "sun": 1,
+    "Packages": 1,
+    "JavaArray": 1,
+    "JavaMember": 1,
+    "JavaObject": 1,
+    "JavaClass": 1,
+    "JavaPackage": 1,
+    "_firebug": 1,
+    "_FirebugConsole": 1,
+    "_FirebugCommandLine": 1
+};
+
+if (Firebug.ignoreFirebugElements)
+    ignoreVars[Firebug.Lite.Cache.ID] = 1;
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+var memberPanelRep =
+    isIE6 ?
+    {"class": "memberLabel $member.type\\Label", href: "javacript:void(0)"}
+    :
+    {"class": "memberLabel $member.type\\Label"};
+
+var RowTag =
+    TR({"class": "memberRow $member.open $member.type\\Row", $hasChildren: "$member.hasChildren", role : 'presentation',
+        level: "$member.level"},
+        TD({"class": "memberLabelCell", style: "padding-left: $member.indent\\px", role : 'presentation'},
+            A(memberPanelRep,
+                SPAN({}, "$member.name")
+            )
+        ),
+        TD({"class": "memberValueCell", role : 'presentation'},
+            TAG("$member.tag", {object: "$member.value"})
+        )
+    );
+
+var WatchRowTag =
+    TR({"class": "watchNewRow", level: 0},
+        TD({"class": "watchEditCell", colspan: 2},
+            DIV({"class": "watchEditBox a11yFocusNoTab", role: "button", 'tabindex' : '0',
+                'aria-label' : $STR('press enter to add new watch expression')},
+                    $STR("NewWatch")
+            )
+        )
+    );
+
+var SizerRow =
+    TR({role : 'presentation'},
+        TD({width: "30%"}),
+        TD({width: "70%"})
+    );
+
+var domTableClass = isIElt8 ? "domTable domTableIE" : "domTable";
+var DirTablePlate = domplate(Firebug.Rep,
+{
+    tag:
+        TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0, onclick: "$onClick", role :"tree"},
+            TBODY({role: 'presentation'},
+                SizerRow,
+                FOR("member", "$object|memberIterator", RowTag)
+            )
+        ),
+
+    watchTag:
+        TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0,
+               _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'},
+            TBODY({role : 'presentation'},
+                SizerRow,
+                WatchRowTag
+            )
+        ),
+
+    tableTag:
+        TABLE({"class": domTableClass, cellpadding: 0, cellspacing: 0,
+            _toggles: "$toggles", _domPanel: "$domPanel", onclick: "$onClick", role : 'tree'},
+            TBODY({role : 'presentation'},
+                SizerRow
+            )
+        ),
+
+    rowTag:
+        FOR("member", "$members", RowTag),
+
+    memberIterator: function(object, level)
+    {
+        return getMembers(object, level);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onClick: function(event)
+    {
+        if (!isLeftClick(event))
+            return;
+
+        var target = event.target || event.srcElement;
+
+        var row = getAncestorByClass(target, "memberRow");
+        var label = getAncestorByClass(target, "memberLabel");
+        if (label && hasClass(row, "hasChildren"))
+        {
+            var row = label.parentNode.parentNode;
+            this.toggleRow(row);
+        }
+        else
+        {
+            var object = Firebug.getRepObject(target);
+            if (typeof(object) == "function")
+            {
+                Firebug.chrome.select(object, "script");
+                cancelEvent(event);
+            }
+            else if (event.detail == 2 && !object)
+            {
+                var panel = row.parentNode.parentNode.domPanel;
+                if (panel)
+                {
+                    var rowValue = panel.getRowPropertyValue(row);
+                    if (typeof(rowValue) == "boolean")
+                        panel.setPropertyValue(row, !rowValue);
+                    else
+                        panel.editProperty(row);
+
+                    cancelEvent(event);
+                }
+            }
+        }
+
+        return false;
+    },
+
+    toggleRow: function(row)
+    {
+        var level = parseInt(row.getAttribute("level"));
+        var toggles = row.parentNode.parentNode.toggles;
+
+        if (hasClass(row, "opened"))
+        {
+            removeClass(row, "opened");
+
+            if (toggles)
+            {
+                var path = getPath(row);
+
+                // Remove the path from the toggle tree
+                for (var i = 0; i < path.length; ++i)
+                {
+                    if (i == path.length-1)
+                        delete toggles[path[i]];
+                    else
+                        toggles = toggles[path[i]];
+                }
+            }
+
+            var rowTag = this.rowTag;
+            var tbody = row.parentNode;
+
+            setTimeout(function()
+            {
+                for (var firstRow = row.nextSibling; firstRow; firstRow = row.nextSibling)
+                {
+                    if (parseInt(firstRow.getAttribute("level")) <= level)
+                        break;
+
+                    tbody.removeChild(firstRow);
+                }
+            }, row.insertTimeout ? row.insertTimeout : 0);
+        }
+        else
+        {
+            setClass(row, "opened");
+
+            if (toggles)
+            {
+                var path = getPath(row);
+
+                // Mark the path in the toggle tree
+                for (var i = 0; i < path.length; ++i)
+                {
+                    var name = path[i];
+                    if (toggles.hasOwnProperty(name))
+                        toggles = toggles[name];
+                    else
+                        toggles = toggles[name] = {};
+                }
+            }
+
+            var value = row.lastChild.firstChild.repObject;
+            var members = getMembers(value, level+1);
+
+            var rowTag = this.rowTag;
+            var lastRow = row;
+
+            var delay = 0;
+            //var setSize = members.length;
+            //var rowCount = 1;
+            while (members.length)
+            {
+                with({slice: members.splice(0, insertSliceSize), isLast: !members.length})
+                {
+                    setTimeout(function()
+                    {
+                        if (lastRow.parentNode)
+                        {
+                            var result = rowTag.insertRows({members: slice}, lastRow);
+                            lastRow = result[1];
+                            //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [null, result, rowCount, setSize]);
+                            //rowCount += insertSliceSize;
+                        }
+                        if (isLast)
+                            row.removeAttribute("insertTimeout");
+                    }, delay);
+                }
+
+                delay += insertInterval;
+            }
+
+            row.insertTimeout = delay;
+        }
+    }
+});
+
+
+
+// ************************************************************************************************
+
+Firebug.DOMBasePanel = function() {};
+
+Firebug.DOMBasePanel.prototype = extend(Firebug.Panel,
+{
+    tag: DirTablePlate.tableTag,
+
+    getRealObject: function(object)
+    {
+        // TODO: Move this to some global location
+        // TODO: Unwrapping should be centralized rather than sprinkling it around ad hoc.
+        // TODO: We might be able to make this check more authoritative with QueryInterface.
+        if (!object) return object;
+        if (object.wrappedJSObject) return object.wrappedJSObject;
+        return object;
+    },
+
+    rebuild: function(update, scrollTop)
+    {
+        //dispatch([Firebug.A11yModel], 'onBeforeDomUpdateSelection', [this]);
+        var members = getMembers(this.selection);
+        expandMembers(members, this.toggles, 0, 0);
+
+        this.showMembers(members, update, scrollTop);
+
+        //TODO: xxxpedro statusbar
+        if (!this.parentPanel)
+            updateStatusBar(this);
+    },
+
+    showMembers: function(members, update, scrollTop)
+    {
+        // If we are still in the midst of inserting rows, cancel all pending
+        // insertions here - this is a big speedup when stepping in the debugger
+        if (this.timeouts)
+        {
+            for (var i = 0; i < this.timeouts.length; ++i)
+                this.context.clearTimeout(this.timeouts[i]);
+            delete this.timeouts;
+        }
+
+        if (!members.length)
+            return this.showEmptyMembers();
+
+        var panelNode = this.panelNode;
+        var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop;
+
+        // If we are asked to "update" the current view, then build the new table
+        // offscreen and swap it in when it's done
+        var offscreen = update && panelNode.firstChild;
+        var dest = offscreen ? panelNode.ownerDocument : panelNode;
+
+        var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest);
+        var tbody = table.lastChild;
+        var rowTag = DirTablePlate.rowTag;
+
+        // Insert the first slice immediately
+        //var slice = members.splice(0, insertSliceSize);
+        //var result = rowTag.insertRows({members: slice}, tbody.lastChild);
+
+        //var setSize = members.length;
+        //var rowCount = 1;
+
+        var panel = this;
+        var result;
+
+        //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
+        var timeouts = [];
+
+        var delay = 0;
+
+        // enable to measure rendering performance
+        var renderStart = new Date().getTime();
+        while (members.length)
+        {
+            with({slice: members.splice(0, insertSliceSize), isLast: !members.length})
+            {
+                timeouts.push(this.context.setTimeout(function()
+                {
+                    // TODO: xxxpedro can this be a timing error related to the
+                    // "iteration number" approach insted of "duration time"?
+                    // avoid error in IE8
+                    if (!tbody.lastChild) return;
+
+                    result = rowTag.insertRows({members: slice}, tbody.lastChild);
+
+                    //rowCount += insertSliceSize;
+                    //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
+
+                    if ((panelNode.scrollHeight+panelNode.offsetHeight) >= priorScrollTop)
+                        panelNode.scrollTop = priorScrollTop;
+
+
+                    // enable to measure rendering performance
+                    //if (isLast) alert(new Date().getTime() - renderStart + "ms");
+
+
+                }, delay));
+
+                delay += insertInterval;
+            }
+        }
+
+        if (offscreen)
+        {
+            timeouts.push(this.context.setTimeout(function()
+            {
+                if (panelNode.firstChild)
+                    panelNode.replaceChild(table, panelNode.firstChild);
+                else
+                    panelNode.appendChild(table);
+
+                // Scroll back to where we were before
+                panelNode.scrollTop = priorScrollTop;
+            }, delay));
+        }
+        else
+        {
+            timeouts.push(this.context.setTimeout(function()
+            {
+                panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop;
+            }, delay));
+        }
+        this.timeouts = timeouts;
+    },
+
+    /*
+    // new
+    showMembers: function(members, update, scrollTop)
+    {
+        // If we are still in the midst of inserting rows, cancel all pending
+        // insertions here - this is a big speedup when stepping in the debugger
+        if (this.timeouts)
+        {
+            for (var i = 0; i < this.timeouts.length; ++i)
+                this.context.clearTimeout(this.timeouts[i]);
+            delete this.timeouts;
+        }
+
+        if (!members.length)
+            return this.showEmptyMembers();
+
+        var panelNode = this.panelNode;
+        var priorScrollTop = scrollTop == undefined ? panelNode.scrollTop : scrollTop;
+
+        // If we are asked to "update" the current view, then build the new table
+        // offscreen and swap it in when it's done
+        var offscreen = update && panelNode.firstChild;
+        var dest = offscreen ? panelNode.ownerDocument : panelNode;
+
+        var table = this.tag.replace({domPanel: this, toggles: this.toggles}, dest);
+        var tbody = table.lastChild;
+        var rowTag = DirTablePlate.rowTag;
+
+        // Insert the first slice immediately
+        //var slice = members.splice(0, insertSliceSize);
+        //var result = rowTag.insertRows({members: slice}, tbody.lastChild);
+
+        //var setSize = members.length;
+        //var rowCount = 1;
+
+        var panel = this;
+        var result;
+
+        //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
+        var timeouts = [];
+
+        var delay = 0;
+        var _insertSliceSize = insertSliceSize;
+        var _insertInterval = insertInterval;
+
+        // enable to measure rendering performance
+        var renderStart = new Date().getTime();
+        var lastSkip = renderStart, now;
+
+        while (members.length)
+        {
+            with({slice: members.splice(0, _insertSliceSize), isLast: !members.length})
+            {
+                var _tbody = tbody;
+                var _rowTag = rowTag;
+                var _panelNode = panelNode;
+                var _priorScrollTop = priorScrollTop;
+
+                timeouts.push(this.context.setTimeout(function()
+                {
+                    // TODO: xxxpedro can this be a timing error related to the
+                    // "iteration number" approach insted of "duration time"?
+                    // avoid error in IE8
+                    if (!_tbody.lastChild) return;
+
+                    result = _rowTag.insertRows({members: slice}, _tbody.lastChild);
+
+                    //rowCount += _insertSliceSize;
+                    //dispatch([Firebug.A11yModel], 'onMemberRowSliceAdded', [panel, result, rowCount, setSize]);
+
+                    if ((_panelNode.scrollHeight + _panelNode.offsetHeight) >= _priorScrollTop)
+                        _panelNode.scrollTop = _priorScrollTop;
+
+
+                    // enable to measure rendering performance
+                    //alert("gap: " + (new Date().getTime() - lastSkip));
+                    //lastSkip = new Date().getTime();
+
+                    //if (isLast) alert("new: " + (new Date().getTime() - renderStart) + "ms");
+
+                }, delay));
+
+                delay += _insertInterval;
+            }
+        }
+
+        if (offscreen)
+        {
+            timeouts.push(this.context.setTimeout(function()
+            {
+                if (panelNode.firstChild)
+                    panelNode.replaceChild(table, panelNode.firstChild);
+                else
+                    panelNode.appendChild(table);
+
+                // Scroll back to where we were before
+                panelNode.scrollTop = priorScrollTop;
+            }, delay));
+        }
+        else
+        {
+            timeouts.push(this.context.setTimeout(function()
+            {
+                panelNode.scrollTop = scrollTop == undefined ? 0 : scrollTop;
+            }, delay));
+        }
+        this.timeouts = timeouts;
+    },
+    /**/
+
+    showEmptyMembers: function()
+    {
+        FirebugReps.Warning.tag.replace({object: "NoMembersWarning"}, this.panelNode);
+    },
+
+    findPathObject: function(object)
+    {
+        var pathIndex = -1;
+        for (var i = 0; i < this.objectPath.length; ++i)
+        {
+            // IE needs === instead of == or otherwise some objects will
+            // be considered equal to different objects, returning the
+            // wrong index of the objectPath array
+            if (this.getPathObject(i) === object)
+                return i;
+        }
+
+        return -1;
+    },
+
+    getPathObject: function(index)
+    {
+        var object = this.objectPath[index];
+
+        if (object instanceof Property)
+            return object.getObject();
+        else
+            return object;
+    },
+
+    getRowObject: function(row)
+    {
+        var object = getRowOwnerObject(row);
+        return object ? object : this.selection;
+    },
+
+    getRowPropertyValue: function(row)
+    {
+        var object = this.getRowObject(row);
+        object = this.getRealObject(object);
+        if (object)
+        {
+            var propName = getRowName(row);
+
+            if (object instanceof jsdIStackFrame)
+                return Firebug.Debugger.evaluate(propName, this.context);
+            else
+                return object[propName];
+        }
+    },
+    /*
+    copyProperty: function(row)
+    {
+        var value = this.getRowPropertyValue(row);
+        copyToClipboard(value);
+    },
+
+    editProperty: function(row, editValue)
+    {
+        if (hasClass(row, "watchNewRow"))
+        {
+            if (this.context.stopped)
+                Firebug.Editor.startEditing(row, "");
+            else if (Firebug.Console.isAlwaysEnabled())  // not stopped in debugger, need command line
+            {
+                if (Firebug.CommandLine.onCommandLineFocus())
+                    Firebug.Editor.startEditing(row, "");
+                else
+                    row.innerHTML = $STR("warning.Command line blocked?");
+            }
+            else
+                row.innerHTML = $STR("warning.Console must be enabled");
+        }
+        else if (hasClass(row, "watchRow"))
+            Firebug.Editor.startEditing(row, getRowName(row));
+        else
+        {
+            var object = this.getRowObject(row);
+            this.context.thisValue = object;
+
+            if (!editValue)
+            {
+                var propValue = this.getRowPropertyValue(row);
+
+                var type = typeof(propValue);
+                if (type == "undefined" || type == "number" || type == "boolean")
+                    editValue = propValue;
+                else if (type == "string")
+                    editValue = "\"" + escapeJS(propValue) + "\"";
+                else if (propValue == null)
+                    editValue = "null";
+                else if (object instanceof Window || object instanceof jsdIStackFrame)
+                    editValue = getRowName(row);
+                else
+                    editValue = "this." + getRowName(row);
+            }
+
+
+            Firebug.Editor.startEditing(row, editValue);
+        }
+    },
+
+    deleteProperty: function(row)
+    {
+        if (hasClass(row, "watchRow"))
+            this.deleteWatch(row);
+        else
+        {
+            var object = getRowOwnerObject(row);
+            if (!object)
+                object = this.selection;
+            object = this.getRealObject(object);
+
+            if (object)
+            {
+                var name = getRowName(row);
+                try
+                {
+                    delete object[name];
+                }
+                catch (exc)
+                {
+                    return;
+                }
+
+                this.rebuild(true);
+                this.markChange();
+            }
+        }
+    },
+
+    setPropertyValue: function(row, value)  // value must be string
+    {
+        if(FBTrace.DBG_DOM)
+        {
+            FBTrace.sysout("row: "+row);
+            FBTrace.sysout("value: "+value+" type "+typeof(value), value);
+        }
+
+        var name = getRowName(row);
+        if (name == "this")
+            return;
+
+        var object = this.getRowObject(row);
+        object = this.getRealObject(object);
+        if (object && !(object instanceof jsdIStackFrame))
+        {
+             // unwrappedJSObject.property = unwrappedJSObject
+             Firebug.CommandLine.evaluate(value, this.context, object, this.context.getGlobalScope(),
+                 function success(result, context)
+                 {
+                     if (FBTrace.DBG_DOM)
+                         FBTrace.sysout("setPropertyValue evaluate success object["+name+"]="+result+" type "+typeof(result), result);
+                     object[name] = result;
+                 },
+                 function failed(exc, context)
+                 {
+                     try
+                     {
+                         if (FBTrace.DBG_DOM)
+                              FBTrace.sysout("setPropertyValue evaluate failed with exc:"+exc+" object["+name+"]="+value+" type "+typeof(value), exc);
+                         // If the value doesn't parse, then just store it as a string.  Some users will
+                         // not realize they're supposed to enter a JavaScript expression and just type
+                         // literal text
+                         object[name] = String(value);  // unwrappedJSobject.property = string
+                     }
+                     catch (exc)
+                     {
+                         return;
+                     }
+                  }
+             );
+        }
+        else if (this.context.stopped)
+        {
+            try
+            {
+                Firebug.CommandLine.evaluate(name+"="+value, this.context);
+            }
+            catch (exc)
+            {
+                try
+                {
+                    // See catch block above...
+                    object[name] = String(value); // unwrappedJSobject.property = string
+                }
+                catch (exc)
+                {
+                    return;
+                }
+            }
+        }
+
+        this.rebuild(true);
+        this.markChange();
+    },
+
+    highlightRow: function(row)
+    {
+        if (this.highlightedRow)
+            cancelClassTimed(this.highlightedRow, "jumpHighlight", this.context);
+
+        this.highlightedRow = row;
+
+        if (row)
+            setClassTimed(row, "jumpHighlight", this.context);
+    },/**/
+
+    onMouseMove: function(event)
+    {
+        var target = event.srcElement || event.target;
+
+        var object = getAncestorByClass(target, "objectLink-element");
+        object = object ? object.repObject : null;
+
+        if(object && instanceOf(object, "Element") && object.nodeType == 1)
+        {
+            if(object != lastHighlightedObject)
+            {
+                Firebug.Inspector.drawBoxModel(object);
+                object = lastHighlightedObject;
+            }
+        }
+        else
+            Firebug.Inspector.hideBoxModel();
+
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    create: function()
+    {
+        // TODO: xxxpedro
+        this.context = Firebug.browser;
+
+        this.objectPath = [];
+        this.propertyPath = [];
+        this.viewPath = [];
+        this.pathIndex = -1;
+        this.toggles = {};
+
+        Firebug.Panel.create.apply(this, arguments);
+
+        this.panelNode.style.padding = "0 1px";
+    },
+
+    initialize: function(){
+        Firebug.Panel.initialize.apply(this, arguments);
+
+        addEvent(this.panelNode, "mousemove", this.onMouseMove);
+    },
+
+    shutdown: function()
+    {
+        removeEvent(this.panelNode, "mousemove", this.onMouseMove);
+
+        Firebug.Panel.shutdown.apply(this, arguments);
+    },
+
+    /*
+    destroy: function(state)
+    {
+        var view = this.viewPath[this.pathIndex];
+        if (view && this.panelNode.scrollTop)
+            view.scrollTop = this.panelNode.scrollTop;
+
+        if (this.pathIndex)
+            state.pathIndex = this.pathIndex;
+        if (this.viewPath)
+            state.viewPath = this.viewPath;
+        if (this.propertyPath)
+            state.propertyPath = this.propertyPath;
+
+        if (this.propertyPath.length > 0 && !this.propertyPath[1])
+            state.firstSelection = persistObject(this.getPathObject(1), this.context);
+
+        Firebug.Panel.destroy.apply(this, arguments);
+    },
+    /**/
+
+    ishow: function(state)
+    {
+        if (this.context.loaded && !this.selection)
+        {
+            if (!state)
+            {
+                this.select(null);
+                return;
+            }
+            if (state.viewPath)
+                this.viewPath = state.viewPath;
+            if (state.propertyPath)
+                this.propertyPath = state.propertyPath;
+
+            var defaultObject = this.getDefaultSelection(this.context);
+            var selectObject = defaultObject;
+
+            if (state.firstSelection)
+            {
+                var restored = state.firstSelection(this.context);
+                if (restored)
+                {
+                    selectObject = restored;
+                    this.objectPath = [defaultObject, restored];
+                }
+                else
+                    this.objectPath = [defaultObject];
+            }
+            else
+                this.objectPath = [defaultObject];
+
+            if (this.propertyPath.length > 1)
+            {
+                for (var i = 1; i < this.propertyPath.length; ++i)
+                {
+                    var name = this.propertyPath[i];
+                    if (!name)
+                        continue;
+
+                    var object = selectObject;
+                    try
+                    {
+                        selectObject = object[name];
+                    }
+                    catch (exc)
+                    {
+                        selectObject = null;
+                    }
+
+                    if (selectObject)
+                    {
+                        this.objectPath.push(new Property(object, name));
+                    }
+                    else
+                    {
+                        // If we can't access a property, just stop
+                        this.viewPath.splice(i);
+                        this.propertyPath.splice(i);
+                        this.objectPath.splice(i);
+                        selectObject = this.getPathObject(this.objectPath.length-1);
+                        break;
+                    }
+                }
+            }
+
+            var selection = state.pathIndex <= this.objectPath.length-1
+                ? this.getPathObject(state.pathIndex)
+                : this.getPathObject(this.objectPath.length-1);
+
+            this.select(selection);
+        }
+    },
+    /*
+    hide: function()
+    {
+        var view = this.viewPath[this.pathIndex];
+        if (view && this.panelNode.scrollTop)
+            view.scrollTop = this.panelNode.scrollTop;
+    },
+    /**/
+
+    supportsObject: function(object)
+    {
+        if (object == null)
+            return 1000;
+
+        if (typeof(object) == "undefined")
+            return 1000;
+        else if (object instanceof SourceLink)
+            return 0;
+        else
+            return 1; // just agree to support everything but not agressively.
+    },
+
+    refresh: function()
+    {
+        this.rebuild(true);
+    },
+
+    updateSelection: function(object)
+    {
+        var previousIndex = this.pathIndex;
+        var previousView = previousIndex == -1 ? null : this.viewPath[previousIndex];
+
+        var newPath = this.pathToAppend;
+        delete this.pathToAppend;
+
+        var pathIndex = this.findPathObject(object);
+        if (newPath || pathIndex == -1)
+        {
+            this.toggles = {};
+
+            if (newPath)
+            {
+                // Remove everything after the point where we are inserting, so we
+                // essentially replace it with the new path
+                if (previousView)
+                {
+                    if (this.panelNode.scrollTop)
+                        previousView.scrollTop = this.panelNode.scrollTop;
+
+                    var start = previousIndex + 1,
+                        // Opera needs the length argument in splice(), otherwise
+                        // it will consider that only one element should be removed
+                        length = this.objectPath.length - start;
+
+                    this.objectPath.splice(start, length);
+                    this.propertyPath.splice(start, length);
+                    this.viewPath.splice(start, length);
+                }
+
+                var value = this.getPathObject(previousIndex);
+                if (!value)
+                {
+                    if (FBTrace.DBG_ERRORS)
+                        FBTrace.sysout("dom.updateSelection no pathObject for "+previousIndex+"\n");
+                    return;
+                }
+
+                for (var i = 0, length = newPath.length; i < length; ++i)
+                {
+                    var name = newPath[i];
+                    var object = value;
+                    try
+                    {
+                        value = value[name];
+                    }
+                    catch(exc)
+                    {
+                        if (FBTrace.DBG_ERRORS)
+                                FBTrace.sysout("dom.updateSelection FAILS at path_i="+i+" for name:"+name+"\n");
+                        return;
+                    }
+
+                    ++this.pathIndex;
+                    this.objectPath.push(new Property(object, name));
+                    this.propertyPath.push(name);
+                    this.viewPath.push({toggles: this.toggles, scrollTop: 0});
+                }
+            }
+            else
+            {
+                this.toggles = {};
+
+                var win = Firebug.browser.window;
+                //var win = this.context.getGlobalScope();
+                if (object === win)
+                {
+                    this.pathIndex = 0;
+                    this.objectPath = [win];
+                    this.propertyPath = [null];
+                    this.viewPath = [{toggles: this.toggles, scrollTop: 0}];
+                }
+                else
+                {
+                    this.pathIndex = 1;
+                    this.objectPath = [win, object];
+                    this.propertyPath = [null, null];
+                    this.viewPath = [
+                        {toggles: {}, scrollTop: 0},
+                        {toggles: this.toggles, scrollTop: 0}
+                    ];
+                }
+            }
+
+            this.panelNode.scrollTop = 0;
+            this.rebuild();
+        }
+        else
+        {
+            this.pathIndex = pathIndex;
+
+            var view = this.viewPath[pathIndex];
+            this.toggles = view.toggles;
+
+            // Persist the current scroll location
+            if (previousView && this.panelNode.scrollTop)
+                previousView.scrollTop = this.panelNode.scrollTop;
+
+            this.rebuild(false, view.scrollTop);
+        }
+    },
+
+    getObjectPath: function(object)
+    {
+        return this.objectPath;
+    },
+
+    getDefaultSelection: function()
+    {
+        return Firebug.browser.window;
+        //return this.context.getGlobalScope();
+    }/*,
+
+    updateOption: function(name, value)
+    {
+        const optionMap = {showUserProps: 1, showUserFuncs: 1, showDOMProps: 1,
+            showDOMFuncs: 1, showDOMConstants: 1};
+        if ( optionMap.hasOwnProperty(name) )
+            this.rebuild(true);
+    },
+
+    getOptionsMenuItems: function()
+    {
+        return [
+            optionMenu("ShowUserProps", "showUserProps"),
+            optionMenu("ShowUserFuncs", "showUserFuncs"),
+            optionMenu("ShowDOMProps", "showDOMProps"),
+            optionMenu("ShowDOMFuncs", "showDOMFuncs"),
+            optionMenu("ShowDOMConstants", "showDOMConstants"),
+            "-",
+            {label: "Refresh", command: bindFixed(this.rebuild, this, true) }
+        ];
+    },
+
+    getContextMenuItems: function(object, target)
+    {
+        var row = getAncestorByClass(target, "memberRow");
+
+        var items = [];
+
+        if (row)
+        {
+            var rowName = getRowName(row);
+            var rowObject = this.getRowObject(row);
+            var rowValue = this.getRowPropertyValue(row);
+
+            var isWatch = hasClass(row, "watchRow");
+            var isStackFrame = rowObject instanceof jsdIStackFrame;
+
+            if (typeof(rowValue) == "string" || typeof(rowValue) == "number")
+            {
+                // Functions already have a copy item in their context menu
+                items.push(
+                    "-",
+                    {label: "CopyValue",
+                        command: bindFixed(this.copyProperty, this, row) }
+                );
+            }
+
+            items.push(
+                "-",
+                {label: isWatch ? "EditWatch" : (isStackFrame ? "EditVariable" : "EditProperty"),
+                    command: bindFixed(this.editProperty, this, row) }
+            );
+
+            if (isWatch || (!isStackFrame && !isDOMMember(rowObject, rowName)))
+            {
+                items.push(
+                    {label: isWatch ? "DeleteWatch" : "DeleteProperty",
+                        command: bindFixed(this.deleteProperty, this, row) }
+                );
+            }
+        }
+
+        items.push(
+            "-",
+            {label: "Refresh", command: bindFixed(this.rebuild, this, true) }
+        );
+
+        return items;
+    },
+
+    getEditor: function(target, value)
+    {
+        if (!this.editor)
+            this.editor = new DOMEditor(this.document);
+
+        return this.editor;
+    }/**/
+});
+
+// ************************************************************************************************
+
+// TODO: xxxpedro statusbar
+var updateStatusBar = function(panel)
+{
+    var path = panel.propertyPath;
+    var index = panel.pathIndex;
+
+    var r = [];
+
+    for (var i=0, l=path.length; i<l; i++)
+    {
+        r.push(i==index ? '<a class="fbHover fbButton fbBtnSelected" ' : '<a class="fbHover fbButton" ');
+        r.push('pathIndex=');
+        r.push(i);
+
+        if(isIE6)
+            r.push(' href="javascript:void(0)"');
+
+        r.push('>');
+        r.push(i==0 ? "window" : path[i] || "Object");
+        r.push('</a>');
+
+        if(i < l-1)
+            r.push('<span class="fbStatusSeparator">&gt;</span>');
+    }
+    panel.statusBarNode.innerHTML = r.join("");
+};
+
+
+var DOMMainPanel = Firebug.DOMPanel = function () {};
+
+Firebug.DOMPanel.DirTable = DirTablePlate;
+
+DOMMainPanel.prototype = extend(Firebug.DOMBasePanel.prototype,
+{
+    onClickStatusBar: function(event)
+    {
+        var target = event.srcElement || event.target;
+        var element = getAncestorByClass(target, "fbHover");
+
+        if(element)
+        {
+            var pathIndex = element.getAttribute("pathIndex");
+
+            if(pathIndex)
+            {
+                this.select(this.getPathObject(pathIndex));
+            }
+        }
+    },
+
+    selectRow: function(row, target)
+    {
+        if (!target)
+            target = row.lastChild.firstChild;
+
+        if (!target || !target.repObject)
+            return;
+
+        this.pathToAppend = getPath(row);
+
+        // If the object is inside an array, look up its index
+        var valueBox = row.lastChild.firstChild;
+        if (hasClass(valueBox, "objectBox-array"))
+        {
+            var arrayIndex = FirebugReps.Arr.getItemIndex(target);
+            this.pathToAppend.push(arrayIndex);
+        }
+
+        // Make sure we get a fresh status path for the object, since otherwise
+        // it might find the object in the existing path and not refresh it
+        //Firebug.chrome.clearStatusPath();
+
+        this.select(target.repObject, true);
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onClick: function(event)
+    {
+        var target = event.srcElement || event.target;
+        var repNode = Firebug.getRepNode(target);
+        if (repNode)
+        {
+            var row = getAncestorByClass(target, "memberRow");
+            if (row)
+            {
+                this.selectRow(row, repNode);
+                cancelEvent(event);
+            }
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    name: "DOM",
+    title: "DOM",
+    searchable: true,
+    statusSeparator: ">",
+
+    options: {
+        hasToolButtons: true,
+        hasStatusBar: true
+    },
+
+    create: function()
+    {
+        Firebug.DOMBasePanel.prototype.create.apply(this, arguments);
+
+        this.onClick = bind(this.onClick, this);
+
+        //TODO: xxxpedro
+        this.onClickStatusBar = bind(this.onClickStatusBar, this);
+
+        this.panelNode.style.padding = "0 1px";
+    },
+
+    initialize: function(oldPanelNode)
+    {
+        //this.panelNode.addEventListener("click", this.onClick, false);
+        //dispatch([Firebug.A11yModel], 'onInitializeNode', [this, 'console']);
+
+        Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
+
+        addEvent(this.panelNode, "click", this.onClick);
+
+        // TODO: xxxpedro dom
+        this.ishow();
+
+        //TODO: xxxpedro
+        addEvent(this.statusBarNode, "click", this.onClickStatusBar);
+    },
+
+    shutdown: function()
+    {
+        //this.panelNode.removeEventListener("click", this.onClick, false);
+        //dispatch([Firebug.A11yModel], 'onDestroyNode', [this, 'console']);
+
+        removeEvent(this.panelNode, "click", this.onClick);
+
+        Firebug.DOMBasePanel.prototype.shutdown.apply(this, arguments);
+    }/*,
+
+    search: function(text, reverse)
+    {
+        if (!text)
+        {
+            delete this.currentSearch;
+            this.highlightRow(null);
+            return false;
+        }
+
+        var row;
+        if (this.currentSearch && text == this.currentSearch.text)
+            row = this.currentSearch.findNext(true, undefined, reverse, Firebug.searchCaseSensitive);
+        else
+        {
+            function findRow(node) { return getAncestorByClass(node, "memberRow"); }
+            this.currentSearch = new TextSearch(this.panelNode, findRow);
+            row = this.currentSearch.find(text, reverse, Firebug.searchCaseSensitive);
+        }
+
+        if (row)
+        {
+            var sel = this.document.defaultView.getSelection();
+            sel.removeAllRanges();
+            sel.addRange(this.currentSearch.range);
+
+            scrollIntoCenterView(row, this.panelNode);
+
+            this.highlightRow(row);
+            dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, row]);
+            return true;
+        }
+        else
+        {
+            dispatch([Firebug.A11yModel], 'onDomSearchMatchFound', [this, text, null]);
+            return false;
+        }
+    }/**/
+});
+
+Firebug.registerPanel(DOMMainPanel);
+
+
+// ************************************************************************************************
+
+
+
+// ************************************************************************************************
+// Local Helpers
+
+var getMembers = function getMembers(object, level)  // we expect object to be user-level object wrapped in security blanket
+{
+    if (!level)
+        level = 0;
+
+    var ordinals = [], userProps = [], userClasses = [], userFuncs = [],
+        domProps = [], domFuncs = [], domConstants = [];
+
+    try
+    {
+        var domMembers = getDOMMembers(object);
+        //var domMembers = {}; // TODO: xxxpedro
+        //var domConstantMap = {};  // TODO: xxxpedro
+
+        if (object.wrappedJSObject)
+            var insecureObject = object.wrappedJSObject;
+        else
+            var insecureObject = object;
+
+        // IE function prototype is not listed in (for..in)
+        if (isIE && isFunction(object))
+            addMember("user", userProps, "prototype", object.prototype, level);
+
+        for (var name in insecureObject)  // enumeration is safe
+        {
+            if (ignoreVars[name] == 1)  // javascript.options.strict says ignoreVars is undefined.
+                continue;
+
+            var val;
+            try
+            {
+                val = insecureObject[name];  // getter is safe
+            }
+            catch (exc)
+            {
+                // Sometimes we get exceptions trying to access certain members
+                if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM)
+                    FBTrace.sysout("dom.getMembers cannot access "+name, exc);
+            }
+
+            var ordinal = parseInt(name);
+            if (ordinal || ordinal == 0)
+            {
+                addMember("ordinal", ordinals, name, val, level);
+            }
+            else if (isFunction(val))
+            {
+                if (isClassFunction(val) && !(name in domMembers))
+                    addMember("userClass", userClasses, name, val, level);
+                else if (name in domMembers)
+                    addMember("domFunction", domFuncs, name, val, level, domMembers[name]);
+                else
+                    addMember("userFunction", userFuncs, name, val, level);
+            }
+            else
+            {
+                //TODO: xxxpedro
+                /*
+                var getterFunction = insecureObject.__lookupGetter__(name),
+                    setterFunction = insecureObject.__lookupSetter__(name),
+                    prefix = "";
+
+                if(getterFunction && !setterFunction)
+                    prefix = "get ";
+                /**/
+
+                var prefix = "";
+
+                if (name in domMembers && !(name in domConstantMap))
+                    addMember("dom", domProps, (prefix+name), val, level, domMembers[name]);
+                else if (name in domConstantMap)
+                    addMember("dom", domConstants, (prefix+name), val, level);
+                else
+                    addMember("user", userProps, (prefix+name), val, level);
+            }
+        }
+    }
+    catch (exc)
+    {
+        // Sometimes we get exceptions just from trying to iterate the members
+        // of certain objects, like StorageList, but don't let that gum up the works
+        throw exc;
+        if (FBTrace.DBG_ERRORS && FBTrace.DBG_DOM)
+            FBTrace.sysout("dom.getMembers FAILS: ", exc);
+        //throw exc;
+    }
+
+    function sortName(a, b) { return a.name > b.name ? 1 : -1; }
+    function sortOrder(a, b) { return a.order > b.order ? 1 : -1; }
+
+    var members = [];
+
+    members.push.apply(members, ordinals);
+
+    Firebug.showUserProps = true; // TODO: xxxpedro
+    Firebug.showUserFuncs = true; // TODO: xxxpedro
+    Firebug.showDOMProps = true;
+    Firebug.showDOMFuncs = true;
+    Firebug.showDOMConstants = true;
+
+    if (Firebug.showUserProps)
+    {
+        userProps.sort(sortName);
+        members.push.apply(members, userProps);
+    }
+
+    if (Firebug.showUserFuncs)
+    {
+        userClasses.sort(sortName);
+        members.push.apply(members, userClasses);
+
+        userFuncs.sort(sortName);
+        members.push.apply(members, userFuncs);
+    }
+
+    if (Firebug.showDOMProps)
+    {
+        domProps.sort(sortName);
+        members.push.apply(members, domProps);
+    }
+
+    if (Firebug.showDOMFuncs)
+    {
+        domFuncs.sort(sortName);
+        members.push.apply(members, domFuncs);
+    }
+
+    if (Firebug.showDOMConstants)
+        members.push.apply(members, domConstants);
+
+    return members;
+};
+
+function expandMembers(members, toggles, offset, level)  // recursion starts with offset=0, level=0
+{
+    var expanded = 0;
+    for (var i = offset; i < members.length; ++i)
+    {
+        var member = members[i];
+        if (member.level > level)
+            break;
+
+        if ( toggles.hasOwnProperty(member.name) )
+        {
+            member.open = "opened";  // member.level <= level && member.name in toggles.
+
+            var newMembers = getMembers(member.value, level+1);  // sets newMembers.level to level+1
+
+            var args = [i+1, 0];
+            args.push.apply(args, newMembers);
+            members.splice.apply(members, args);
+
+            /*
+            if (FBTrace.DBG_DOM)
+            {
+                FBTrace.sysout("expandMembers member.name", member.name);
+                FBTrace.sysout("expandMembers toggles", toggles);
+                FBTrace.sysout("expandMembers toggles[member.name]", toggles[member.name]);
+                FBTrace.sysout("dom.expandedMembers level: "+level+" member", member);
+            }
+            /**/
+
+            expanded += newMembers.length;
+            i += newMembers.length + expandMembers(members, toggles[member.name], i+1, level+1);
+        }
+    }
+
+    return expanded;
+}
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+
+function isClassFunction(fn)
+{
+    try
+    {
+        for (var name in fn.prototype)
+            return true;
+    } catch (exc) {}
+    return false;
+}
+
+// FIXME: xxxpedro This function is already defined in Lib. If we keep this definition here, it
+// will crash IE9 when not running the IE Developer Tool with JavaScript Debugging enabled!!!
+// Check if this function is in fact defined in Firebug for Firefox. If so, we should remove
+// this from here. The only difference of this function is the IE hack to show up the prototype
+// of functions, but Firebug no longer shows the prototype for simple functions.
+//var hasProperties = function hasProperties(ob)
+//{
+//    try
+//    {
+//        for (var name in ob)
+//            return true;
+//    } catch (exc) {}
+//
+//    // IE function prototype is not listed in (for..in)
+//    if (isFunction(ob)) return true;
+//
+//    return false;
+//};
+
+FBL.ErrorCopy = function(message)
+{
+    this.message = message;
+};
+
+var addMember = function addMember(type, props, name, value, level, order)
+{
+    var rep = Firebug.getRep(value);    // do this first in case a call to instanceof reveals contents
+    var tag = rep.shortTag ? rep.shortTag : rep.tag;
+
+    var ErrorCopy = function(){}; //TODO: xxxpedro
+
+    var valueType = typeof(value);
+    var hasChildren = hasProperties(value) && !(value instanceof ErrorCopy) &&
+        (isFunction(value) || (valueType == "object" && value != null)
+        || (valueType == "string" && value.length > Firebug.stringCropLength));
+
+    props.push({
+        name: name,
+        value: value,
+        type: type,
+        rowClass: "memberRow-"+type,
+        open: "",
+        order: order,
+        level: level,
+        indent: level*16,
+        hasChildren: hasChildren,
+        tag: tag
+    });
+};
+
+var getWatchRowIndex = function getWatchRowIndex(row)
+{
+    var index = -1;
+    for (; row && hasClass(row, "watchRow"); row = row.previousSibling)
+        ++index;
+    return index;
+};
+
+var getRowName = function getRowName(row)
+{
+    var node = row.firstChild;
+    return node.textContent ? node.textContent : node.innerText;
+};
+
+var getRowValue = function getRowValue(row)
+{
+    return row.lastChild.firstChild.repObject;
+};
+
+var getRowOwnerObject = function getRowOwnerObject(row)
+{
+    var parentRow = getParentRow(row);
+    if (parentRow)
+        return getRowValue(parentRow);
+};
+
+var getParentRow = function getParentRow(row)
+{
+    var level = parseInt(row.getAttribute("level"))-1;
+    for (row = row.previousSibling; row; row = row.previousSibling)
+    {
+        if (parseInt(row.getAttribute("level")) == level)
+            return row;
+    }
+};
+
+var getPath = function getPath(row)
+{
+    var name = getRowName(row);
+    var path = [name];
+
+    var level = parseInt(row.getAttribute("level"))-1;
+    for (row = row.previousSibling; row; row = row.previousSibling)
+    {
+        if (parseInt(row.getAttribute("level")) == level)
+        {
+            var name = getRowName(row);
+            path.splice(0, 0, name);
+
+            --level;
+        }
+    }
+
+    return path;
+};
+
+// ************************************************************************************************
+
+
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+
+// ************************************************************************************************
+// DOM Module
+
+Firebug.DOM = extend(Firebug.Module,
+{
+    getPanel: function()
+    {
+        return Firebug.chrome ? Firebug.chrome.getPanel("DOM") : null;
+    }
+});
+
+Firebug.registerModule(Firebug.DOM);
+
+
+// ************************************************************************************************
+// DOM Panel
+
+var lastHighlightedObject;
+
+function DOMSidePanel(){};
+
+DOMSidePanel.prototype = extend(Firebug.DOMBasePanel.prototype,
+{
+    selectRow: function(row, target)
+    {
+        if (!target)
+            target = row.lastChild.firstChild;
+
+        if (!target || !target.repObject)
+            return;
+
+        this.pathToAppend = getPath(row);
+
+        // If the object is inside an array, look up its index
+        var valueBox = row.lastChild.firstChild;
+        if (hasClass(valueBox, "objectBox-array"))
+        {
+            var arrayIndex = FirebugReps.Arr.getItemIndex(target);
+            this.pathToAppend.push(arrayIndex);
+        }
+
+        // Make sure we get a fresh status path for the object, since otherwise
+        // it might find the object in the existing path and not refresh it
+        //Firebug.chrome.clearStatusPath();
+
+        var object = target.repObject;
+
+        if (instanceOf(object, "Element"))
+        {
+            Firebug.HTML.selectTreeNode(ElementCache(object));
+        }
+        else
+        {
+            Firebug.chrome.selectPanel("DOM");
+            Firebug.chrome.getPanel("DOM").select(object, true);
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+
+    onClick: function(event)
+    {
+        /*
+        var target = event.srcElement || event.target;
+
+        var object = getAncestorByClass(target, "objectLink");
+        object = object ? object.repObject : null;
+
+        if(!object) return;
+
+        if (instanceOf(object, "Element"))
+        {
+            Firebug.HTML.selectTreeNode(ElementCache(object));
+        }
+        else
+        {
+            Firebug.chrome.selectPanel("DOM");
+            Firebug.chrome.getPanel("DOM").select(object, true);
+        }
+        /**/
+
+
+        var target = event.srcElement || event.target;
+        var repNode = Firebug.getRepNode(target);
+        if (repNode)
+        {
+            var row = getAncestorByClass(target, "memberRow");
+            if (row)
+            {
+                this.selectRow(row, repNode);
+                cancelEvent(event);
+            }
+        }
+        /**/
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // extends Panel
+
+    name: "DOMSidePanel",
+    parentPanel: "HTML",
+    title: "DOM",
+
+    options: {
+        hasToolButtons: true
+    },
+
+    isInitialized: false,
+
+    create: function()
+    {
+        Firebug.DOMBasePanel.prototype.create.apply(this, arguments);
+
+        this.onClick = bind(this.onClick, this);
+    },
+
+    initialize: function(){
+        Firebug.DOMBasePanel.prototype.initialize.apply(this, arguments);
+
+        addEvent(this.panelNode, "click", this.onClick);
+
+        // TODO: xxxpedro css2
+        var selection = ElementCache.get(Firebug.context.persistedState.selectedHTMLElementId);
+        if (selection)
+            this.select(selection, true);
+    },
+
+    shutdown: function()
+    {
+        removeEvent(this.panelNode, "click", this.onClick);
+
+        Firebug.DOMBasePanel.prototype.shutdown.apply(this, arguments);
+    },
+
+    reattach: function(oldChrome)
+    {
+        //this.isInitialized = oldChrome.getPanel("DOM").isInitialized;
+        this.toggles = oldChrome.getPanel("DOMSidePanel").toggles;
+    }
+
+});
+
+Firebug.registerPanel(DOMSidePanel);
+
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.FBTrace = {};
+
+(function() {
+// ************************************************************************************************
+
+var traceOptions = {
+    DBG_TIMESTAMP: 1,
+    DBG_INITIALIZE: 1,
+    DBG_CHROME: 1,
+    DBG_ERRORS: 1,
+    DBG_DISPATCH: 1,
+    DBG_CSS: 1
+};
+
+this.module = null;
+
+this.initialize = function()
+{
+    if (!this.messageQueue)
+        this.messageQueue = [];
+
+    for (var name in traceOptions)
+        this[name] = traceOptions[name];
+};
+
+// ************************************************************************************************
+// FBTrace API
+
+this.sysout = function()
+{
+    return this.logFormatted(arguments, "");
+};
+
+this.dumpProperties = function(title, object)
+{
+    return this.logFormatted("dumpProperties() not supported.", "warning");
+};
+
+this.dumpStack = function()
+{
+    return this.logFormatted("dumpStack() not supported.", "warning");
+};
+
+this.flush = function(module)
+{
+    this.module = module;
+
+    var queue = this.messageQueue;
+    this.messageQueue = [];
+
+    for (var i = 0; i < queue.length; ++i)
+        this.writeMessage(queue[i][0], queue[i][1], queue[i][2]);
+};
+
+this.getPanel = function()
+{
+    return this.module ? this.module.getPanel() : null;
+};
+
+//*************************************************************************************************
+
+this.logFormatted = function(objects, className)
+{
+    var html = this.DBG_TIMESTAMP ? [getTimestamp(), " | "] : [];
+    var length = objects.length;
+
+    for (var i = 0; i < length; ++i)
+    {
+        appendText(" ", html);
+
+        var object = objects[i];
+
+        if (i == 0)
+        {
+            html.push("<b>");
+            appendText(object, html);
+            html.push("</b>");
+        }
+        else
+            appendText(object, html);
+    }
+
+    return this.logRow(html, className);
+};
+
+this.logRow = function(message, className)
+{
+    var panel = this.getPanel();
+
+    if (panel && panel.panelNode)
+        this.writeMessage(message, className);
+    else
+    {
+        this.messageQueue.push([message, className]);
+    }
+
+    return this.LOG_COMMAND;
+};
+
+this.writeMessage = function(message, className)
+{
+    var container = this.getPanel().containerNode;
+    var isScrolledToBottom =
+        container.scrollTop + container.offsetHeight >= container.scrollHeight;
+
+    this.writeRow.call(this, message, className);
+
+    if (isScrolledToBottom)
+        container.scrollTop = container.scrollHeight - container.offsetHeight;
+};
+
+this.appendRow = function(row)
+{
+    var container = this.getPanel().panelNode;
+    container.appendChild(row);
+};
+
+this.writeRow = function(message, className)
+{
+    var row = this.getPanel().panelNode.ownerDocument.createElement("div");
+    row.className = "logRow" + (className ? " logRow-"+className : "");
+    row.innerHTML = message.join("");
+    this.appendRow(row);
+};
+
+//*************************************************************************************************
+
+function appendText(object, html)
+{
+    html.push(escapeHTML(objectToString(object)));
+};
+
+function getTimestamp()
+{
+    var now = new Date();
+    var ms = "" + (now.getMilliseconds() / 1000).toFixed(3);
+    ms = ms.substr(2);
+
+    return now.toLocaleTimeString() + "." + ms;
+};
+
+//*************************************************************************************************
+
+var HTMLtoEntity =
+{
+    "<": "&lt;",
+    ">": "&gt;",
+    "&": "&amp;",
+    "'": "&#39;",
+    '"': "&quot;"
+};
+
+function replaceChars(ch)
+{
+    return HTMLtoEntity[ch];
+};
+
+function escapeHTML(value)
+{
+    return (value+"").replace(/[<>&"']/g, replaceChars);
+};
+
+//*************************************************************************************************
+
+function objectToString(object)
+{
+    try
+    {
+        return object+"";
+    }
+    catch (exc)
+    {
+        return null;
+    }
+};
+
+// ************************************************************************************************
+}).apply(FBL.FBTrace);
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// If application isn't in trace mode, the FBTrace panel won't be loaded
+if (!Env.Options.enableTrace) return;
+
+// ************************************************************************************************
+// FBTrace Module
+
+Firebug.Trace = extend(Firebug.Module,
+{
+    getPanel: function()
+    {
+        return Firebug.chrome ? Firebug.chrome.getPanel("Trace") : null;
+    },
+
+    clear: function()
+    {
+        this.getPanel().panelNode.innerHTML = "";
+    }
+});
+
+Firebug.registerModule(Firebug.Trace);
+
+
+// ************************************************************************************************
+// FBTrace Panel
+
+function TracePanel(){};
+
+TracePanel.prototype = extend(Firebug.Panel,
+{
+    name: "Trace",
+    title: "Trace",
+
+    options: {
+        hasToolButtons: true,
+        innerHTMLSync: true
+    },
+
+    create: function(){
+        Firebug.Panel.create.apply(this, arguments);
+
+        this.clearButton = new Button({
+            caption: "Clear",
+            title: "Clear FBTrace logs",
+            owner: Firebug.Trace,
+            onClick: Firebug.Trace.clear
+        });
+    },
+
+    initialize: function(){
+        Firebug.Panel.initialize.apply(this, arguments);
+
+        this.clearButton.initialize();
+    },
+
+    shutdown: function()
+    {
+        this.clearButton.shutdown();
+
+        Firebug.Panel.shutdown.apply(this, arguments);
+    }
+
+});
+
+Firebug.registerPanel(TracePanel);
+
+// ************************************************************************************************
+}});
+
+/* See license.txt for terms of usage */
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+// ************************************************************************************************
+// Globals
+
+var modules = [];
+var panelTypes = [];
+var panelTypeMap = {};
+
+var parentPanelMap = {};
+
+
+var registerModule = Firebug.registerModule;
+var registerPanel = Firebug.registerPanel;
+
+// ************************************************************************************************
+append(Firebug,
+{
+    extend: function(fn)
+    {
+        if (Firebug.chrome && Firebug.chrome.addPanel)
+        {
+            var namespace = ns(fn);
+            fn.call(namespace, FBL);
+        }
+        else
+        {
+            setTimeout(function(){Firebug.extend(fn);},100);
+        }
+    },
+
+    // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
+    // Registration
+
+    registerModule: function()
+    {
+        registerModule.apply(Firebug, arguments);
+
+        modules.push.apply(modules, arguments);
+
+        dispatch(modules, "initialize", []);
+
+        if (FBTrace.DBG_INITIALIZE) FBTrace.sysout("Firebug.registerModule");
+    },
+
+    registerPanel: function()
+    {
+        registerPanel.apply(Firebug, arguments);
+
+        panelTypes.push.apply(panelTypes, arguments);
+
+        for (var i = 0, panelType; panelType = arguments[i]; ++i)
+        {
+            // TODO: xxxpedro investigate why Dev Panel throws an error
+            if (panelType.prototype.name == "Dev") continue;
+
+            panelTypeMap[panelType.prototype.name] = arguments[i];
+
+            var parentPanelName = panelType.prototype.parentPanel;
+            if (parentPanelName)
+            {
+                parentPanelMap[parentPanelName] = 1;
+            }
+            else
+            {
+                var panelName = panelType.prototype.name;
+                var chrome = Firebug.chrome;
+                chrome.addPanel(panelName);
+
+                // tab click handler
+                var onTabClick = function onTabClick()
+                {
+                    chrome.selectPanel(panelName);
+                    return false;
+                };
+
+                chrome.addController([chrome.panelMap[panelName].tabNode, "mousedown", onTabClick]);
+            }
+        }
+
+        if (FBTrace.DBG_INITIALIZE)
+            for (var i = 0; i < arguments.length; ++i)
+                FBTrace.sysout("Firebug.registerPanel", arguments[i].prototype.name);
+    }
+
+});
+
+
+
+
+// ************************************************************************************************
+}});
+
+FBL.ns(function() { with (FBL) {
+// ************************************************************************************************
+
+FirebugChrome.Skin =
+{
+    CSS: '.obscured{left:-999999px !important;}.collapsed{display:none;}[collapsed="true"]{display:none;}#fbCSS{padding:0 !important;}.cssPropDisable{float:left;display:block;width:2em;cursor:default;}.infoTip{z-index:2147483647;position:fixed;padding:2px 3px;border:1px solid #CBE087;background:LightYellow;font-family:Monaco,monospace;color:#000000;display:none;white-space:nowrap;pointer-events:none;}.infoTip[active="true"]{display:block;}.infoTipLoading{width:16px;height:16px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/loading_16.gif) no-repeat;}.infoTipImageBox{font-size:11px;min-width:100px;text-align:center;}.infoTipCaption{font-size:11px;font:Monaco,monospace;}.infoTipLoading > .infoTipImage,.infoTipLoading > .infoTipCaption{display:none;}h1.groupHeader{padding:2px 4px;margin:0 0 4px 0;border-top:1px solid #CCCCCC;border-bottom:1px solid #CCCCCC;background:#eee url(https://getfirebug.com/releases/lite/latest/skin/xp/group.gif) repeat-x;font-size:11px;font-weight:bold;_position:relative;}.inlineEditor,.fixedWidthEditor{z-index:2147483647;position:absolute;display:none;}.inlineEditor{margin-left:-6px;margin-top:-3px;}.textEditorInner,.fixedWidthEditor{margin:0 0 0 0 !important;padding:0;border:none !important;font:inherit;text-decoration:inherit;background-color:#FFFFFF;}.fixedWidthEditor{border-top:1px solid #888888 !important;border-bottom:1px solid #888888 !important;}.textEditorInner{position:relative;top:-7px;left:-5px;outline:none;resize:none;}.textEditorInner1{padding-left:11px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.png) repeat-y;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.gif) repeat-y;_overflow:hidden;}.textEditorInner2{position:relative;padding-right:2px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.png) repeat-y 100% 0;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorBorders.gif) repeat-y 100% 0;_position:fixed;}.textEditorTop1{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 100% 0;margin-left:11px;height:10px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 100% 0;_overflow:hidden;}.textEditorTop2{position:relative;left:-11px;width:11px;height:10px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat;}.textEditorBottom1{position:relative;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 100% 100%;margin-left:11px;height:12px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 100% 100%;}.textEditorBottom2{position:relative;left:-11px;width:11px;height:12px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.png) no-repeat 0 100%;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/textEditorCorners.gif) no-repeat 0 100%;}.panelNode-css{overflow-x:hidden;}.cssSheet > .insertBefore{height:1.5em;}.cssRule{position:relative;margin:0;padding:1em 0 0 6px;font-family:Monaco,monospace;color:#000000;}.cssRule:first-child{padding-top:6px;}.cssElementRuleContainer{position:relative;}.cssHead{padding-right:150px;}.cssProp{}.cssPropName{color:DarkGreen;}.cssPropValue{margin-left:8px;color:DarkBlue;}.cssOverridden span{text-decoration:line-through;}.cssInheritedRule{}.cssInheritLabel{margin-right:0.5em;font-weight:bold;}.cssRule .objectLink-sourceLink{top:0;}.cssProp.editGroup:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disable.png) no-repeat 2px 1px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disable.gif) no-repeat 2px 1px;}.cssProp.editGroup.editing{background:none;}.cssProp.disabledStyle{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disableHover.png) no-repeat 2px 1px;_background:url(https://getfirebug.com/releases/lite/latest/skin/xp/disableHover.gif) no-repeat 2px 1px;opacity:1;color:#CCCCCC;}.disabledStyle .cssPropName,.disabledStyle .cssPropValue{color:#CCCCCC;}.cssPropValue.editing + .cssSemi,.inlineExpander + .cssSemi{display:none;}.cssPropValue.editing{white-space:nowrap;}.stylePropName{font-weight:bold;padding:0 4px 4px 4px;width:50%;}.stylePropValue{width:50%;}.panelNode-net{overflow-x:hidden;}.netTable{width:100%;}.hideCategory-undefined .category-undefined,.hideCategory-html .category-html,.hideCategory-css .category-css,.hideCategory-js .category-js,.hideCategory-image .category-image,.hideCategory-xhr .category-xhr,.hideCategory-flash .category-flash,.hideCategory-txt .category-txt,.hideCategory-bin .category-bin{display:none;}.netHeadRow{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/group.gif) repeat-x #FFFFFF;}.netHeadCol{border-bottom:1px solid #CCCCCC;padding:2px 4px 2px 18px;font-weight:bold;}.netHeadLabel{white-space:nowrap;overflow:hidden;}.netHeaderRow{height:16px;}.netHeaderCell{cursor:pointer;-moz-user-select:none;border-bottom:1px solid #9C9C9C;padding:0 !important;font-weight:bold;background:#BBBBBB url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeader.gif) repeat-x;white-space:nowrap;}.netHeaderRow > .netHeaderCell:first-child > .netHeaderCellBox{padding:2px 14px 2px 18px;}.netHeaderCellBox{padding:2px 14px 2px 10px;border-left:1px solid #D9D9D9;border-right:1px solid #9C9C9C;}.netHeaderCell:hover:active{background:#959595 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderActive.gif) repeat-x;}.netHeaderSorted{background:#7D93B2 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderSorted.gif) repeat-x;}.netHeaderSorted > .netHeaderCellBox{border-right-color:#6B7C93;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/arrowDown.png) no-repeat right;}.netHeaderSorted.sortedAscending > .netHeaderCellBox{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/arrowUp.png);}.netHeaderSorted:hover:active{background:#536B90 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/tableHeaderSortedActive.gif) repeat-x;}.panelNode-net .netRowHeader{display:block;}.netRowHeader{cursor:pointer;display:none;height:15px;margin-right:0 !important;}.netRow .netRowHeader{background-position:5px 1px;}.netRow[breakpoint="true"] .netRowHeader{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/breakpoint.png);}.netRow[breakpoint="true"][disabledBreakpoint="true"] .netRowHeader{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/breakpointDisabled.png);}.netRow.category-xhr:hover .netRowHeader{background-color:#F6F6F6;}#netBreakpointBar{max-width:38px;}#netHrefCol > .netHeaderCellBox{border-left:0px;}.netRow .netRowHeader{width:3px;}.netInfoRow .netRowHeader{display:table-cell;}.netTable[hiddenCols~=netHrefCol] TD[id="netHrefCol"],.netTable[hiddenCols~=netHrefCol] TD.netHrefCol,.netTable[hiddenCols~=netStatusCol] TD[id="netStatusCol"],.netTable[hiddenCols~=netStatusCol] TD.netStatusCol,.netTable[hiddenCols~=netDomainCol] TD[id="netDomainCol"],.netTable[hiddenCols~=netDomainCol] TD.netDomainCol,.netTable[hiddenCols~=netSizeCol] TD[id="netSizeCol"],.netTable[hiddenCols~=netSizeCol] TD.netSizeCol,.netTable[hiddenCols~=netTimeCol] TD[id="netTimeCol"],.netTable[hiddenCols~=netTimeCol] TD.netTimeCol{display:none;}.netRow{background:LightYellow;}.netRow.loaded{background:#FFFFFF;}.netRow.loaded:hover{background:#EFEFEF;}.netCol{padding:0;vertical-align:top;border-bottom:1px solid #EFEFEF;white-space:nowrap;height:17px;}.netLabel{width:100%;}.netStatusCol{padding-left:10px;color:rgb(128,128,128);}.responseError > .netStatusCol{color:red;}.netDomainCol{padding-left:5px;}.netSizeCol{text-align:right;padding-right:10px;}.netHrefLabel{-moz-box-sizing:padding-box;overflow:hidden;z-index:10;position:absolute;padding-left:18px;padding-top:1px;max-width:15%;font-weight:bold;}.netFullHrefLabel{display:none;-moz-user-select:none;padding-right:10px;padding-bottom:3px;max-width:100%;background:#FFFFFF;z-index:200;}.netHrefCol:hover > .netFullHrefLabel{display:block;}.netRow.loaded:hover .netCol > .netFullHrefLabel{background-color:#EFEFEF;}.useA11y .a11yShowFullLabel{display:block;background-image:none !important;border:1px solid #CBE087;background-color:LightYellow;font-family:Monaco,monospace;color:#000000;font-size:10px;z-index:2147483647;}.netSizeLabel{padding-left:6px;}.netStatusLabel,.netDomainLabel,.netSizeLabel,.netBar{padding:1px 0 2px 0 !important;}.responseError{color:red;}.hasHeaders .netHrefLabel:hover{cursor:pointer;color:blue;text-decoration:underline;}.netLoadingIcon{position:absolute;border:0;margin-left:14px;width:16px;height:16px;background:transparent no-repeat 0 0;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/loading_16.gif);display:inline-block;}.loaded .netLoadingIcon{display:none;}.netBar,.netSummaryBar{position:relative;border-right:50px solid transparent;}.netResolvingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarResolving.gif) repeat-x;z-index:60;}.netConnectingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarConnecting.gif) repeat-x;z-index:50;}.netBlockingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarWaiting.gif) repeat-x;z-index:40;}.netSendingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarSending.gif) repeat-x;z-index:30;}.netWaitingBar{position:absolute;left:0;top:0;bottom:0;background:#FFFFFF url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarResponded.gif) repeat-x;z-index:20;min-width:1px;}.netReceivingBar{position:absolute;left:0;top:0;bottom:0;background:#38D63B url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarLoading.gif) repeat-x;z-index:10;}.netWindowLoadBar,.netContentLoadBar{position:absolute;left:0;top:0;bottom:0;width:1px;background-color:red;z-index:70;opacity:0.5;display:none;margin-bottom:-1px;}.netContentLoadBar{background-color:Blue;}.netTimeLabel{-moz-box-sizing:padding-box;position:absolute;top:1px;left:100%;padding-left:6px;color:#444444;min-width:16px;}.loaded .netReceivingBar,.loaded.netReceivingBar{background:#B6B6B6 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarLoaded.gif) repeat-x;border-color:#B6B6B6;}.fromCache .netReceivingBar,.fromCache.netReceivingBar{background:#D6D6D6 url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/netBarCached.gif) repeat-x;border-color:#D6D6D6;}.netSummaryRow .netTimeLabel,.loaded .netTimeLabel{background:transparent;}.timeInfoTip{width:150px; height:40px}.timeInfoTipBar,.timeInfoTipEventBar{position:relative;display:block;margin:0;opacity:1;height:15px;width:4px;}.timeInfoTipEventBar{width:1px !important;}.timeInfoTipCell.startTime{padding-right:8px;}.timeInfoTipCell.elapsedTime{text-align:right;padding-right:8px;}.sizeInfoLabelCol{font-weight:bold;padding-right:10px;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;}.sizeInfoSizeCol{font-weight:bold;}.sizeInfoDetailCol{color:gray;text-align:right;}.sizeInfoDescCol{font-style:italic;}.netSummaryRow .netReceivingBar{background:#BBBBBB;border:none;}.netSummaryLabel{color:#222222;}.netSummaryRow{background:#BBBBBB !important;font-weight:bold;}.netSummaryRow .netBar{border-right-color:#BBBBBB;}.netSummaryRow > .netCol{border-top:1px solid #999999;border-bottom:2px solid;-moz-border-bottom-colors:#EFEFEF #999999;padding-top:1px;padding-bottom:2px;}.netSummaryRow > .netHrefCol:hover{background:transparent !important;}.netCountLabel{padding-left:18px;}.netTotalSizeCol{text-align:right;padding-right:10px;}.netTotalTimeCol{text-align:right;}.netCacheSizeLabel{position:absolute;z-index:1000;left:0;top:0;}.netLimitRow{background:rgb(255,255,225) !important;font-weight:normal;color:black;font-weight:normal;}.netLimitLabel{padding-left:18px;}.netLimitRow > .netCol{border-bottom:2px solid;-moz-border-bottom-colors:#EFEFEF #999999;vertical-align:middle !important;padding-top:2px;padding-bottom:2px;}.netLimitButton{font-size:11px;padding-top:1px;padding-bottom:1px;}.netInfoCol{border-top:1px solid #EEEEEE;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/group.gif) repeat-x #FFFFFF;}.netInfoBody{margin:10px 0 4px 10px;}.netInfoTabs{position:relative;padding-left:17px;}.netInfoTab{position:relative;top:-3px;margin-top:10px;padding:4px 6px;border:1px solid transparent;border-bottom:none;_border:none;font-weight:bold;color:#565656;cursor:pointer;}.netInfoTabSelected{cursor:default !important;border:1px solid #D7D7D7 !important;border-bottom:none !important;-moz-border-radius:4px 4px 0 0;-webkit-border-radius:4px 4px 0 0;border-radius:4px 4px 0 0;background-color:#FFFFFF;}.logRow-netInfo.error .netInfoTitle{color:red;}.logRow-netInfo.loading .netInfoResponseText{font-style:italic;color:#888888;}.loading .netInfoResponseHeadersTitle{display:none;}.netInfoResponseSizeLimit{font-family:Lucida Grande,Tahoma,sans-serif;padding-top:10px;font-size:11px;}.netInfoText{display:none;margin:0;border:1px solid #D7D7D7;border-right:none;padding:8px;background-color:#FFFFFF;font-family:Monaco,monospace;white-space:pre-wrap;}.netInfoTextSelected{display:block;}.netInfoParamName{padding-right:10px;font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;vertical-align:top;text-align:right;white-space:nowrap;}.netInfoPostText .netInfoParamName{width:1px;}.netInfoParamValue{width:100%;}.netInfoHeadersText,.netInfoPostText,.netInfoPutText{padding-top:0;}.netInfoHeadersGroup,.netInfoPostParams,.netInfoPostSource{margin-bottom:4px;border-bottom:1px solid #D7D7D7;padding-top:8px;padding-bottom:2px;font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;color:#565656;}.netInfoPostParamsTable,.netInfoPostPartsTable,.netInfoPostJSONTable,.netInfoPostXMLTable,.netInfoPostSourceTable{margin-bottom:10px;width:100%;}.netInfoPostContentType{color:#bdbdbd;padding-left:50px;font-weight:normal;}.netInfoHtmlPreview{border:0;width:100%;height:100%;}.netHeadersViewSource{color:#bdbdbd;margin-left:200px;font-weight:normal;}.netHeadersViewSource:hover{color:blue;cursor:pointer;}.netActivationRow,.netPageSeparatorRow{background:rgb(229,229,229) !important;font-weight:normal;color:black;}.netActivationLabel{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/chrome://firebug/skin/infoIcon.png) no-repeat 3px 2px;padding-left:22px;}.netPageSeparatorRow{height:5px !important;}.netPageSeparatorLabel{padding-left:22px;height:5px !important;}.netPageRow{background-color:rgb(255,255,255);}.netPageRow:hover{background:#EFEFEF;}.netPageLabel{padding:1px 0 2px 18px !important;font-weight:bold;}.netActivationRow > .netCol{border-bottom:2px solid;-moz-border-bottom-colors:#EFEFEF #999999;padding-top:2px;padding-bottom:3px;}.twisty,.logRow-errorMessage > .hasTwisty > .errorTitle,.logRow-log > .objectBox-array.hasTwisty,.logRow-spy .spyHead .spyTitle,.logGroup > .logRow,.memberRow.hasChildren > .memberLabelCell > .memberLabel,.hasHeaders .netHrefLabel,.netPageRow > .netCol > .netPageTitle{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);background-repeat:no-repeat;background-position:2px 2px;min-height:12px;}.logRow-errorMessage > .hasTwisty.opened > .errorTitle,.logRow-log > .objectBox-array.hasTwisty.opened,.logRow-spy.opened .spyHead .spyTitle,.logGroup.opened > .logRow,.memberRow.hasChildren.opened > .memberLabelCell > .memberLabel,.nodeBox.highlightOpen > .nodeLabel > .twisty,.nodeBox.open > .nodeLabel > .twisty,.netRow.opened > .netCol > .netHrefLabel,.netPageRow.opened > .netCol > .netPageTitle{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);}.twisty{background-position:4px 4px;}* html .logRow-spy .spyHead .spyTitle,* html .logGroup .logGroupLabel,* html .hasChildren .memberLabelCell .memberLabel,* html .hasHeaders .netHrefLabel{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);background-repeat:no-repeat;background-position:2px 2px;}* html .opened .spyHead .spyTitle,* html .opened .logGroupLabel,* html .opened .memberLabelCell .memberLabel{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);background-repeat:no-repeat;background-position:2px 2px;}.panelNode-console{overflow-x:hidden;}.objectLink{text-decoration:none;}.objectLink:hover{cursor:pointer;text-decoration:underline;}.logRow{position:relative;margin:0;border-bottom:1px solid #D7D7D7;padding:2px 4px 1px 6px;background-color:#FFFFFF;overflow:hidden !important;}.useA11y .logRow:focus{border-bottom:1px solid #000000 !important;outline:none !important;background-color:#FFFFAD !important;}.useA11y .logRow:focus a.objectLink-sourceLink{background-color:#FFFFAD;}.useA11y .a11yFocus:focus,.useA11y .objectBox:focus{outline:2px solid #FF9933;background-color:#FFFFAD;}.useA11y .objectBox-null:focus,.useA11y .objectBox-undefined:focus{background-color:#888888 !important;}.useA11y .logGroup.opened > .logRow{border-bottom:1px solid #ffffff;}.logGroup{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/group.gif) repeat-x #FFFFFF;padding:0 !important;border:none !important;}.logGroupBody{display:none;margin-left:16px;border-left:1px solid #D7D7D7;border-top:1px solid #D7D7D7;background:#FFFFFF;}.logGroup > .logRow{background-color:transparent !important;font-weight:bold;}.logGroup.opened > .logRow{border-bottom:none;}.logGroup.opened > .logGroupBody{display:block;}.logRow-command > .objectBox-text{font-family:Monaco,monospace;color:#0000FF;white-space:pre-wrap;}.logRow-info,.logRow-warn,.logRow-error,.logRow-assert,.logRow-warningMessage,.logRow-errorMessage{padding-left:22px;background-repeat:no-repeat;background-position:4px 2px;}.logRow-assert,.logRow-warningMessage,.logRow-errorMessage{padding-top:0;padding-bottom:0;}.logRow-info,.logRow-info .objectLink-sourceLink{background-color:#FFFFFF;}.logRow-warn,.logRow-warningMessage,.logRow-warn .objectLink-sourceLink,.logRow-warningMessage .objectLink-sourceLink{background-color:cyan;}.logRow-error,.logRow-assert,.logRow-errorMessage,.logRow-error .objectLink-sourceLink,.logRow-errorMessage .objectLink-sourceLink{background-color:LightYellow;}.logRow-error,.logRow-assert,.logRow-errorMessage{color:#FF0000;}.logRow-info{}.logRow-warn,.logRow-warningMessage{}.logRow-error,.logRow-assert,.logRow-errorMessage{}.objectBox-string,.objectBox-text,.objectBox-number,.objectLink-element,.objectLink-textNode,.objectLink-function,.objectBox-stackTrace,.objectLink-profile{font-family:Monaco,monospace;}.objectBox-string,.objectBox-text,.objectLink-textNode{white-space:pre-wrap;}.objectBox-number,.objectLink-styleRule,.objectLink-element,.objectLink-textNode{color:#000088;}.objectBox-string{color:#FF0000;}.objectLink-function,.objectBox-stackTrace,.objectLink-profile{color:DarkGreen;}.objectBox-null,.objectBox-undefined{padding:0 2px;border:1px solid #666666;background-color:#888888;color:#FFFFFF;}.objectBox-exception{padding:0 2px 0 18px;color:red;}.objectLink-sourceLink{position:absolute;right:4px;top:2px;padding-left:8px;font-family:Lucida Grande,sans-serif;font-weight:bold;color:#0000FF;}.errorTitle{margin-top:0px;margin-bottom:1px;padding-top:2px;padding-bottom:2px;}.errorTrace{margin-left:17px;}.errorSourceBox{margin:2px 0;}.errorSource-none{display:none;}.errorSource-syntax > .errorBreak{visibility:hidden;}.errorSource{cursor:pointer;font-family:Monaco,monospace;color:DarkGreen;}.errorSource:hover{text-decoration:underline;}.errorBreak{cursor:pointer;display:none;margin:0 6px 0 0;width:13px;height:14px;vertical-align:bottom;opacity:0.1;}.hasBreakSwitch .errorBreak{display:inline;}.breakForError .errorBreak{opacity:1;}.assertDescription{margin:0;}.logRow-profile > .logRow > .objectBox-text{font-family:Lucida Grande,Tahoma,sans-serif;color:#000000;}.logRow-profile > .logRow > .objectBox-text:last-child{color:#555555;font-style:italic;}.logRow-profile.opened > .logRow{padding-bottom:4px;}.profilerRunning > .logRow{padding-left:22px !important;}.profileSizer{width:100%;overflow-x:auto;overflow-y:scroll;}.profileTable{border-bottom:1px solid #D7D7D7;padding:0 0 4px 0;}.profileTable tr[odd="1"]{background-color:#F5F5F5;vertical-align:middle;}.profileTable a{vertical-align:middle;}.profileTable td{padding:1px 4px 0 4px;}.headerCell{cursor:pointer;-moz-user-select:none;border-bottom:1px solid #9C9C9C;padding:0 !important;font-weight:bold;}.headerCellBox{padding:2px 4px;border-left:1px solid #D9D9D9;border-right:1px solid #9C9C9C;}.headerCell:hover:active{}.headerSorted{}.headerSorted > .headerCellBox{border-right-color:#6B7C93;}.headerSorted.sortedAscending > .headerCellBox{}.headerSorted:hover:active{}.linkCell{text-align:right;}.linkCell > .objectLink-sourceLink{position:static;}.logRow-stackTrace{padding-top:0;background:#f8f8f8;}.logRow-stackTrace > .objectBox-stackFrame{position:relative;padding-top:2px;}.objectLink-object{font-family:Lucida Grande,sans-serif;font-weight:bold;color:DarkGreen;white-space:pre-wrap;}.objectProp-object{color:DarkGreen;}.objectProps{color:#000;font-weight:normal;}.objectPropName{color:#777;}.objectProps .objectProp-string{color:#f55;}.objectProps .objectProp-number{color:#55a;}.objectProps .objectProp-object{color:#585;}.selectorTag,.selectorId,.selectorClass{font-family:Monaco,monospace;font-weight:normal;}.selectorTag{color:#0000FF;}.selectorId{color:DarkBlue;}.selectorClass{color:red;}.selectorHidden > .selectorTag{color:#5F82D9;}.selectorHidden > .selectorId{color:#888888;}.selectorHidden > .selectorClass{color:#D86060;}.selectorValue{font-family:Lucida Grande,sans-serif;font-style:italic;color:#555555;}.panelNode.searching .logRow{display:none;}.logRow.matched{display:block !important;}.logRow.matching{position:absolute;left:-1000px;top:-1000px;max-width:0;max-height:0;overflow:hidden;}.objectLeftBrace,.objectRightBrace,.objectEqual,.objectComma,.arrayLeftBracket,.arrayRightBracket,.arrayComma{font-family:Monaco,monospace;}.objectLeftBrace,.objectRightBrace,.arrayLeftBracket,.arrayRightBracket{font-weight:bold;}.objectLeftBrace,.arrayLeftBracket{margin-right:4px;}.objectRightBrace,.arrayRightBracket{margin-left:4px;}.logRow-dir{padding:0;}.logRow-errorMessage .hasTwisty .errorTitle,.logRow-spy .spyHead .spyTitle,.logGroup .logRow{cursor:pointer;padding-left:18px;background-repeat:no-repeat;background-position:3px 3px;}.logRow-errorMessage > .hasTwisty > .errorTitle{background-position:2px 3px;}.logRow-errorMessage > .hasTwisty > .errorTitle:hover,.logRow-spy .spyHead .spyTitle:hover,.logGroup > .logRow:hover{text-decoration:underline;}.logRow-spy{padding:0 !important;}.logRow-spy,.logRow-spy .objectLink-sourceLink{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/group.gif) repeat-x #FFFFFF;padding-right:4px;right:0;}.logRow-spy.opened{padding-bottom:4px;border-bottom:none;}.spyTitle{color:#000000;font-weight:bold;-moz-box-sizing:padding-box;overflow:hidden;z-index:100;padding-left:18px;}.spyCol{padding:0;white-space:nowrap;height:16px;}.spyTitleCol:hover > .objectLink-sourceLink,.spyTitleCol:hover > .spyTime,.spyTitleCol:hover > .spyStatus,.spyTitleCol:hover > .spyTitle{display:none;}.spyFullTitle{display:none;-moz-user-select:none;max-width:100%;background-color:Transparent;}.spyTitleCol:hover > .spyFullTitle{display:block;}.spyStatus{padding-left:10px;color:rgb(128,128,128);}.spyTime{margin-left:4px;margin-right:4px;color:rgb(128,128,128);}.spyIcon{margin-right:4px;margin-left:4px;width:16px;height:16px;vertical-align:middle;background:transparent no-repeat 0 0;display:none;}.loading .spyHead .spyRow .spyIcon{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/loading_16.gif);display:block;}.logRow-spy.loaded:not(.error) .spyHead .spyRow .spyIcon{width:0;margin:0;}.logRow-spy.error .spyHead .spyRow .spyIcon{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon-sm.png);display:block;background-position:2px 2px;}.logRow-spy .spyHead .netInfoBody{display:none;}.logRow-spy.opened .spyHead .netInfoBody{margin-top:10px;display:block;}.logRow-spy.error .spyTitle,.logRow-spy.error .spyStatus,.logRow-spy.error .spyTime{color:red;}.logRow-spy.loading .spyResponseText{font-style:italic;color:#888888;}.caption{font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;color:#444444;}.warning{padding:10px;font-family:Lucida Grande,Tahoma,sans-serif;font-weight:bold;color:#888888;}.panelNode-dom{overflow-x:hidden !important;}.domTable{font-size:1em;width:100%;table-layout:fixed;background:#fff;}.domTableIE{width:auto;}.memberLabelCell{padding:2px 0 2px 0;vertical-align:top;}.memberValueCell{padding:1px 0 1px 5px;display:block;overflow:hidden;}.memberLabel{display:block;cursor:default;-moz-user-select:none;overflow:hidden;padding-left:18px;background-color:#FFFFFF;text-decoration:none;}.memberRow.hasChildren .memberLabelCell .memberLabel:hover{cursor:pointer;color:blue;text-decoration:underline;}.userLabel{color:#000000;font-weight:bold;}.userClassLabel{color:#E90000;font-weight:bold;}.userFunctionLabel{color:#025E2A;font-weight:bold;}.domLabel{color:#000000;}.domFunctionLabel{color:#025E2A;}.ordinalLabel{color:SlateBlue;font-weight:bold;}.scopesRow{padding:2px 18px;background-color:LightYellow;border-bottom:5px solid #BEBEBE;color:#666666;}.scopesLabel{background-color:LightYellow;}.watchEditCell{padding:2px 18px;background-color:LightYellow;border-bottom:1px solid #BEBEBE;color:#666666;}.editor-watchNewRow,.editor-memberRow{font-family:Monaco,monospace !important;}.editor-memberRow{padding:1px 0 !important;}.editor-watchRow{padding-bottom:0 !important;}.watchRow > .memberLabelCell{font-family:Monaco,monospace;padding-top:1px;padding-bottom:1px;}.watchRow > .memberLabelCell > .memberLabel{background-color:transparent;}.watchRow > .memberValueCell{padding-top:2px;padding-bottom:2px;}.watchRow > .memberLabelCell,.watchRow > .memberValueCell{background-color:#F5F5F5;border-bottom:1px solid #BEBEBE;}.watchToolbox{z-index:2147483647;position:absolute;right:0;padding:1px 2px;}#fbConsole{overflow-x:hidden !important;}#fbCSS{font:1em Monaco,monospace;padding:0 7px;}#fbstylesheetButtons select,#fbScriptButtons select{font:11px Lucida Grande,Tahoma,sans-serif;margin-top:1px;padding-left:3px;background:#fafafa;border:1px inset #fff;width:220px;outline:none;}.Selector{margin-top:10px}.CSSItem{margin-left:4%}.CSSText{padding-left:20px;}.CSSProperty{color:#005500;}.CSSValue{padding-left:5px; color:#000088;}#fbHTMLStatusBar{display:inline;}.fbToolbarButtons{display:none;}.fbStatusSeparator{display:block;float:left;padding-top:4px;}#fbStatusBarBox{display:none;}#fbToolbarContent{display:block;position:absolute;_position:absolute;top:0;padding-top:4px;height:23px;clip:rect(0,2048px,27px,0);}.fbTabMenuTarget{display:none !important;float:left;width:10px;height:10px;margin-top:6px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuTarget.png);}.fbTabMenuTarget:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuTargetHover.png);}.fbShadow{float:left;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/shadowAlpha.png) no-repeat bottom right !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/shadow2.gif) no-repeat bottom right;margin:10px 0 0 10px !important;margin:10px 0 0 5px;}.fbShadowContent{display:block;position:relative;background-color:#fff;border:1px solid #a9a9a9;top:-6px;left:-6px;}.fbMenu{display:none;position:absolute;font-size:11px;line-height:13px;z-index:2147483647;}.fbMenuContent{padding:2px;}.fbMenuSeparator{display:block;position:relative;padding:1px 18px 0;text-decoration:none;color:#000;cursor:default;background:#ACA899;margin:4px 0;}.fbMenuOption{display:block;position:relative;padding:2px 18px;text-decoration:none;color:#000;cursor:default;}.fbMenuOption:hover{color:#fff;background:#316AC5;}.fbMenuGroup{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right 0;}.fbMenuGroup:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right -17px;}.fbMenuGroupSelected{color:#fff;background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuPin.png) no-repeat right -17px;}.fbMenuChecked{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuCheckbox.png) no-repeat 4px 0;}.fbMenuChecked:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuCheckbox.png) no-repeat 4px -17px;}.fbMenuRadioSelected{background:transparent url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuRadio.png) no-repeat 4px 0;}.fbMenuRadioSelected:hover{background:#316AC5 url(https://getfirebug.com/releases/lite/latest/skin/xp/tabMenuRadio.png) no-repeat 4px -17px;}.fbMenuShortcut{padding-right:85px;}.fbMenuShortcutKey{position:absolute;right:0;top:2px;width:77px;}#fbFirebugMenu{top:22px;left:0;}.fbMenuDisabled{color:#ACA899 !important;}#fbFirebugSettingsMenu{left:245px;top:99px;}#fbConsoleMenu{top:42px;left:48px;}.fbIconButton{display:block;}.fbIconButton{display:block;}.fbIconButton{display:block;float:left;height:20px;width:20px;color:#000;margin-right:2px;text-decoration:none;cursor:default;}.fbIconButton:hover{position:relative;top:-1px;left:-1px;margin-right:0;_margin-right:1px;color:#333;border:1px solid #fff;border-bottom:1px solid #bbb;border-right:1px solid #bbb;}.fbIconPressed{position:relative;margin-right:0;_margin-right:1px;top:0 !important;left:0 !important;height:19px;color:#333 !important;border:1px solid #bbb !important;border-bottom:1px solid #cfcfcf !important;border-right:1px solid #ddd !important;}#fbErrorPopup{position:absolute;right:0;bottom:0;height:19px;width:75px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;z-index:999;}#fbErrorPopupContent{position:absolute;right:0;top:1px;height:18px;width:75px;_width:74px;border-left:1px solid #aca899;}#fbErrorIndicator{position:absolute;top:2px;right:5px;}.fbBtnInspectActive{background:#aaa;color:#fff !important;}.fbBody{margin:0;padding:0;overflow:hidden;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;background:#fff;}.clear{clear:both;}#fbMiniChrome{display:none;right:0;height:27px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;margin-left:1px;}#fbMiniContent{display:block;position:relative;left:-1px;right:0;top:1px;height:25px;border-left:1px solid #aca899;}#fbToolbarSearch{float:right;border:1px solid #ccc;margin:0 5px 0 0;background:#fff url(https://getfirebug.com/releases/lite/latest/skin/xp/search.png) no-repeat 4px 2px !important;background:#fff url(https://getfirebug.com/releases/lite/latest/skin/xp/search.gif) no-repeat 4px 2px;padding-left:20px;font-size:11px;}#fbToolbarErrors{float:right;margin:1px 4px 0 0;font-size:11px;}#fbLeftToolbarErrors{float:left;margin:7px 0px 0 5px;font-size:11px;}.fbErrors{padding-left:20px;height:14px;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.png) no-repeat !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.gif) no-repeat;color:#f00;font-weight:bold;}#fbMiniErrors{display:inline;display:none;float:right;margin:5px 2px 0 5px;}#fbMiniIcon{float:right;margin:3px 4px 0;height:20px;width:20px;float:right;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -135px;cursor:pointer;}#fbChrome{font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;position:absolute;_position:static;top:0;left:0;height:100%;width:100%;border-collapse:collapse;border-spacing:0;background:#fff;overflow:hidden;}#fbChrome > tbody > tr > td{padding:0;}#fbTop{height:49px;}#fbToolbar{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;height:27px;font-size:11px;line-height:13px;}#fbPanelBarBox{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;height:22px;}#fbContent{height:100%;vertical-align:top;}#fbBottom{height:18px;background:#fff;}#fbToolbarIcon{float:left;padding:0 5px 0;}#fbToolbarIcon a{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -135px;}#fbToolbarButtons{padding:0 2px 0 5px;}#fbToolbarButtons{padding:0 2px 0 5px;}.fbButton{text-decoration:none;display:block;float:left;color:#000;padding:4px 6px 4px 7px;cursor:default;}.fbButton:hover{color:#333;background:#f5f5ef url(https://getfirebug.com/releases/lite/latest/skin/xp/buttonBg.png);padding:3px 5px 3px 6px;border:1px solid #fff;border-bottom:1px solid #bbb;border-right:1px solid #bbb;}.fbBtnPressed{background:#e3e3db url(https://getfirebug.com/releases/lite/latest/skin/xp/buttonBgHover.png) !important;padding:3px 4px 2px 6px !important;margin:1px 0 0 1px !important;border:1px solid #ACA899 !important;border-color:#ACA899 #ECEBE3 #ECEBE3 #ACA899 !important;}#fbStatusBarBox{top:4px;cursor:default;}.fbToolbarSeparator{overflow:hidden;border:1px solid;border-color:transparent #fff transparent #777;_border-color:#eee #fff #eee #777;height:7px;margin:6px 3px;float:left;}.fbBtnSelected{font-weight:bold;}.fbStatusBar{color:#aca899;}.fbStatusBar a{text-decoration:none;color:black;}.fbStatusBar a:hover{color:blue;cursor:pointer;}#fbWindowButtons{position:absolute;white-space:nowrap;right:0;top:0;height:17px;width:48px;padding:5px;z-index:6;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 0;}#fbPanelBar1{width:1024px; z-index:8;left:0;white-space:nowrap;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;position:absolute;left:4px;}#fbPanelBar2Box{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #dbd9c9 0 -27px;position:absolute;height:22px;width:300px; z-index:9;right:0;}#fbPanelBar2{position:absolute;width:290px; height:22px;padding-left:4px;}.fbPanel{display:none;}#fbPanelBox1,#fbPanelBox2{max-height:inherit;height:100%;font-size:1em;}#fbPanelBox2{background:#fff;}#fbPanelBox2{width:300px;background:#fff;}#fbPanel2{margin-left:6px;background:#fff;}#fbLargeCommandLine{display:none;position:absolute;z-index:9;top:27px;right:0;width:294px;height:201px;border-width:0;margin:0;padding:2px 0 0 2px;resize:none;outline:none;font-size:11px;overflow:auto;border-top:1px solid #B9B7AF;_right:-1px;_border-left:1px solid #fff;}#fbLargeCommandButtons{display:none;background:#ECE9D8;bottom:0;right:0;width:294px;height:21px;padding-top:1px;position:fixed;border-top:1px solid #ACA899;z-index:9;}#fbSmallCommandLineIcon{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/down.png) no-repeat;position:absolute;right:2px;bottom:3px;z-index:99;}#fbSmallCommandLineIcon:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/downHover.png) no-repeat;}.hide{overflow:hidden !important;position:fixed !important;display:none !important;visibility:hidden !important;}#fbCommand{height:18px;}#fbCommandBox{position:fixed;_position:absolute;width:100%;height:18px;bottom:0;overflow:hidden;z-index:9;background:#fff;border:0;border-top:1px solid #ccc;}#fbCommandIcon{position:absolute;color:#00f;top:2px;left:6px;display:inline;font:11px Monaco,monospace;z-index:10;}#fbCommandLine{position:absolute;width:100%;top:0;left:0;border:0;margin:0;padding:2px 0 2px 32px;font:11px Monaco,monospace;z-index:9;outline:none;}#fbLargeCommandLineIcon{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/up.png) no-repeat;position:absolute;right:1px;bottom:1px;z-index:10;}#fbLargeCommandLineIcon:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/upHover.png) no-repeat;}div.fbFitHeight{overflow:auto;position:relative;}.fbSmallButton{overflow:hidden;width:16px;height:16px;display:block;text-decoration:none;cursor:default;}#fbWindowButtons .fbSmallButton{float:right;}#fbWindow_btClose{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/min.png);}#fbWindow_btClose:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/minHover.png);}#fbWindow_btDetach{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/detach.png);}#fbWindow_btDetach:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/detachHover.png);}#fbWindow_btDeactivate{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/off.png);}#fbWindow_btDeactivate:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/offHover.png);}.fbTab{text-decoration:none;display:none;float:left;width:auto;float:left;cursor:default;font-family:Lucida Grande,Tahoma,sans-serif;font-size:11px;line-height:13px;font-weight:bold;height:22px;color:#565656;}.fbPanelBar span{float:left;}.fbPanelBar .fbTabL,.fbPanelBar .fbTabR{height:22px;width:8px;}.fbPanelBar .fbTabText{padding:4px 1px 0;}a.fbTab:hover{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -73px;}a.fbTab:hover .fbTabL{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -16px -96px;}a.fbTab:hover .fbTabR{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -24px -96px;}.fbSelectedTab{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) #f1f2ee 0 -50px !important;color:#000;}.fbSelectedTab .fbTabL{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) 0 -96px !important;}.fbSelectedTab .fbTabR{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/sprite.png) -8px -96px !important;}#fbHSplitter{position:fixed;_position:absolute;left:0;top:0;width:100%;height:5px;overflow:hidden;cursor:n-resize !important;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/pixel_transparent.gif);z-index:9;}#fbHSplitter.fbOnMovingHSplitter{height:100%;z-index:100;}.fbVSplitter{background:#ece9d8;color:#000;border:1px solid #716f64;border-width:0 1px;border-left-color:#aca899;width:4px;cursor:e-resize;overflow:hidden;right:294px;text-decoration:none;z-index:10;position:absolute;height:100%;top:27px;}div.lineNo{font:1em/1.4545em Monaco,monospace;position:relative;float:left;top:0;left:0;margin:0 5px 0 0;padding:0 5px 0 10px;background:#eee;color:#888;border-right:1px solid #ccc;text-align:right;}.sourceBox{position:absolute;}.sourceCode{font:1em Monaco,monospace;overflow:hidden;white-space:pre;display:inline;}.nodeControl{margin-top:3px;margin-left:-14px;float:left;width:9px;height:9px;overflow:hidden;cursor:default;background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_open.gif);_float:none;_display:inline;_position:absolute;}div.nodeMaximized{background:url(https://getfirebug.com/releases/lite/latest/skin/xp/tree_close.gif);}div.objectBox-element{padding:1px 3px;}.objectBox-selector{cursor:default;}.selectedElement{background:highlight;color:#fff !important;}.selectedElement span{color:#fff !important;}* html .selectedElement{position:relative;}@media screen and (-webkit-min-device-pixel-ratio:0){.selectedElement{background:#316AC5;color:#fff !important;}}.logRow *{font-size:1em;}.logRow{position:relative;border-bottom:1px solid #D7D7D7;padding:2px 4px 1px 6px;zbackground-color:#FFFFFF;}.logRow-command{font-family:Monaco,monospace;color:blue;}.objectBox-string,.objectBox-text,.objectBox-number,.objectBox-function,.objectLink-element,.objectLink-textNode,.objectLink-function,.objectBox-stackTrace,.objectLink-profile{font-family:Monaco,monospace;}.objectBox-null{padding:0 2px;border:1px solid #666666;background-color:#888888;color:#FFFFFF;}.objectBox-string{color:red;}.objectBox-number{color:#000088;}.objectBox-function{color:DarkGreen;}.objectBox-object{color:DarkGreen;font-weight:bold;font-family:Lucida Grande,sans-serif;}.objectBox-array{color:#000;}.logRow-info,.logRow-error,.logRow-warn{background:#fff no-repeat 2px 2px;padding-left:20px;padding-bottom:3px;}.logRow-info{background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/infoIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/infoIcon.gif);}.logRow-warn{background-color:cyan;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/warningIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/warningIcon.gif);}.logRow-error{background-color:LightYellow;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.png) !important;background-image:url(https://getfirebug.com/releases/lite/latest/skin/xp/errorIcon.gif);color:#f00;}.errorMessage{vertical-align:top;color:#f00;}.objectBox-sourceLink{position:absolute;right:4px;top:2px;padding-left:8px;font-family:Lucida Grande,sans-serif;font-weight:bold;color:#0000FF;}.selectorTag,.selectorId,.selectorClass{font-family:Monaco,monospace;font-weight:normal;}.selectorTag{color:#0000FF;}.selectorId{color:DarkBlue;}.selectorClass{color:red;}.objectBox-element{font-family:Monaco,monospace;color:#000088;}.nodeChildren{padding-left:26px;}.nodeTag{color:blue;cursor:pointer;}.nodeValue{color:#FF0000;font-weight:normal;}.nodeText,.nodeComment{margin:0 2px;vertical-align:top;}.nodeText{color:#333333;font-family:Monaco,monospace;}.nodeComment{color:DarkGreen;}.nodeHidden,.nodeHidden *{color:#888888;}.nodeHidden .nodeTag{color:#5F82D9;}.nodeHidden .nodeValue{color:#D86060;}.selectedElement .nodeHidden,.selectedElement .nodeHidden *{color:SkyBlue !important;}.log-object{}.property{position:relative;clear:both;height:15px;}.propertyNameCell{vertical-align:top;float:left;width:28%;position:absolute;left:0;z-index:0;}.propertyValueCell{float:right;width:68%;background:#fff;position:absolute;padding-left:5px;display:table-cell;right:0;z-index:1;}.propertyName{font-weight:bold;}.FirebugPopup{height:100% !important;}.FirebugPopup #fbWindowButtons{display:none !important;}.FirebugPopup #fbHSplitter{display:none !important;}',
+    HTML: '<table id="fbChrome" cellpadding="0" cellspacing="0" border="0"><tbody><tr><td id="fbTop" colspan="2"><div id="fbWindowButtons"><a id="fbWindow_btDeactivate" class="fbSmallButton fbHover" title="Deactivate Firebug for this web page">&nbsp;</a><a id="fbWindow_btDetach" class="fbSmallButton fbHover" title="Open Firebug in popup window">&nbsp;</a><a id="fbWindow_btClose" class="fbSmallButton fbHover" title="Minimize Firebug">&nbsp;</a></div><div id="fbToolbar"><div id="fbToolbarContent"><span id="fbToolbarIcon"><a id="fbFirebugButton" class="fbIconButton" class="fbHover" target="_blank">&nbsp;</a></span><span id="fbToolbarButtons"><span id="fbFixedButtons"><a id="fbChrome_btInspect" class="fbButton fbHover" title="Click an element in the page to inspect">Inspect</a></span><span id="fbConsoleButtons" class="fbToolbarButtons"><a id="fbConsole_btClear" class="fbButton fbHover" title="Clear the console">Clear</a></span></span><span id="fbStatusBarBox"><span class="fbToolbarSeparator"></span></span></div></div><div id="fbPanelBarBox"><div id="fbPanelBar1" class="fbPanelBar"><a id="fbConsoleTab" class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">Console</span><span class="fbTabMenuTarget"></span><span class="fbTabR"></span></a><a id="fbHTMLTab" class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">HTML</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">CSS</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">Script</span><span class="fbTabR"></span></a><a class="fbTab fbHover"><span class="fbTabL"></span><span class="fbTabText">DOM</span><span class="fbTabR"></span></a></div><div id="fbPanelBar2Box" class="hide"><div id="fbPanelBar2" class="fbPanelBar"></div></div></div><div id="fbHSplitter">&nbsp;</div></td></tr><tr id="fbContent"><td id="fbPanelBox1"><div id="fbPanel1" class="fbFitHeight"><div id="fbConsole" class="fbPanel"></div><div id="fbHTML" class="fbPanel"></div></div></td><td id="fbPanelBox2" class="hide"><div id="fbVSplitter" class="fbVSplitter">&nbsp;</div><div id="fbPanel2" class="fbFitHeight"><div id="fbHTML_Style" class="fbPanel"></div><div id="fbHTML_Layout" class="fbPanel"></div><div id="fbHTML_DOM" class="fbPanel"></div></div><textarea id="fbLargeCommandLine" class="fbFitHeight"></textarea><div id="fbLargeCommandButtons"><a id="fbCommand_btRun" class="fbButton fbHover">Run</a><a id="fbCommand_btClear" class="fbButton fbHover">Clear</a><a id="fbSmallCommandLineIcon" class="fbSmallButton fbHover"></a></div></td></tr><tr id="fbBottom" class="hide"><td id="fbCommand" colspan="2"><div id="fbCommandBox"><div id="fbCommandIcon">&gt;&gt;&gt;</div><input id="fbCommandLine" name="fbCommandLine" type="text"/><a id="fbLargeCommandLineIcon" class="fbSmallButton fbHover"></a></div></td></tr></tbody></table><span id="fbMiniChrome"><span id="fbMiniContent"><span id="fbMiniIcon" title="Open Firebug Lite"></span><span id="fbMiniErrors" class="fbErrors"></span></span></span>'
+};
+
+// ************************************************************************************************
+}});
+
+// ************************************************************************************************
+FBL.initialize();
+// ************************************************************************************************
+
+})();
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/json-js/json2.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/json-js/json2.js
new file mode 100644
index 0000000..5838457
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/json-js/json2.js
@@ -0,0 +1,519 @@
+/*
+    json2.js
+    2015-05-03
+
+    Public Domain.
+
+    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+
+    See http://www.JSON.org/js.html
+
+
+    This code should be minified before deployment.
+    See http://javascript.crockford.com/jsmin.html
+
+    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
+    NOT CONTROL.
+
+
+    This file creates a global JSON object containing two methods: stringify
+    and parse. This file is provides the ES5 JSON capability to ES3 systems.
+    If a project might run on IE8 or earlier, then this file should be included.
+    This file does nothing on ES5 systems.
+
+        JSON.stringify(value, replacer, space)
+            value       any JavaScript value, usually an object or array.
+
+            replacer    an optional parameter that determines how object
+                        values are stringified for objects. It can be a
+                        function or an array of strings.
+
+            space       an optional parameter that specifies the indentation
+                        of nested structures. If it is omitted, the text will
+                        be packed without extra whitespace. If it is a number,
+                        it will specify the number of spaces to indent at each
+                        level. If it is a string (such as '\t' or '&nbsp;'),
+                        it contains the characters used to indent at each level.
+
+            This method produces a JSON text from a JavaScript value.
+
+            When an object value is found, if the object contains a toJSON
+            method, its toJSON method will be called and the result will be
+            stringified. A toJSON method does not serialize: it returns the
+            value represented by the name/value pair that should be serialized,
+            or undefined if nothing should be serialized. The toJSON method
+            will be passed the key associated with the value, and this will be
+            bound to the value
+
+            For example, this would serialize Dates as ISO strings.
+
+                Date.prototype.toJSON = function (key) {
+                    function f(n) {
+                        // Format integers to have at least two digits.
+                        return n < 10 
+                            ? '0' + n 
+                            : n;
+                    }
+
+                    return this.getUTCFullYear()   + '-' +
+                         f(this.getUTCMonth() + 1) + '-' +
+                         f(this.getUTCDate())      + 'T' +
+                         f(this.getUTCHours())     + ':' +
+                         f(this.getUTCMinutes())   + ':' +
+                         f(this.getUTCSeconds())   + 'Z';
+                };
+
+            You can provide an optional replacer method. It will be passed the
+            key and value of each member, with this bound to the containing
+            object. The value that is returned from your method will be
+            serialized. If your method returns undefined, then the member will
+            be excluded from the serialization.
+
+            If the replacer parameter is an array of strings, then it will be
+            used to select the members to be serialized. It filters the results
+            such that only members with keys listed in the replacer array are
+            stringified.
+
+            Values that do not have JSON representations, such as undefined or
+            functions, will not be serialized. Such values in objects will be
+            dropped; in arrays they will be replaced with null. You can use
+            a replacer function to replace those with JSON values.
+            JSON.stringify(undefined) returns undefined.
+
+            The optional space parameter produces a stringification of the
+            value that is filled with line breaks and indentation to make it
+            easier to read.
+
+            If the space parameter is a non-empty string, then that string will
+            be used for indentation. If the space parameter is a number, then
+            the indentation will be that many spaces.
+
+            Example:
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}]);
+            // text is '["e",{"pluribus":"unum"}]'
+
+
+            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
+            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'
+
+            text = JSON.stringify([new Date()], function (key, value) {
+                return this[key] instanceof Date 
+                    ? 'Date(' + this[key] + ')' 
+                    : value;
+            });
+            // text is '["Date(---current time---)"]'
+
+
+        JSON.parse(text, reviver)
+            This method parses a JSON text to produce an object or array.
+            It can throw a SyntaxError exception.
+
+            The optional reviver parameter is a function that can filter and
+            transform the results. It receives each of the keys and values,
+            and its return value is used instead of the original value.
+            If it returns what it received, then the structure is not modified.
+            If it returns undefined then the member is deleted.
+
+            Example:
+
+            // Parse the text. Values that look like ISO date strings will
+            // be converted to Date objects.
+
+            myData = JSON.parse(text, function (key, value) {
+                var a;
+                if (typeof value === 'string') {
+                    a =
+/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
+                    if (a) {
+                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
+                            +a[5], +a[6]));
+                    }
+                }
+                return value;
+            });
+
+            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
+                var d;
+                if (typeof value === 'string' &&
+                        value.slice(0, 5) === 'Date(' &&
+                        value.slice(-1) === ')') {
+                    d = new Date(value.slice(5, -1));
+                    if (d) {
+                        return d;
+                    }
+                }
+                return value;
+            });
+
+
+    This is a reference implementation. You are free to copy, modify, or
+    redistribute.
+*/
+
+/*jslint 
+    eval, for, this 
+*/
+
+/*property
+    JSON, apply, call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
+    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
+    lastIndex, length, parse, prototype, push, replace, slice, stringify,
+    test, toJSON, toString, valueOf
+*/
+
+
+// Create a JSON object only if one does not already exist. We create the
+// methods in a closure to avoid creating global variables.
+
+if (typeof JSON !== 'object') {
+    JSON = {};
+}
+
+(function () {
+    'use strict';
+    
+    var rx_one = /^[\],:{}\s]*$/,
+        rx_two = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g,
+        rx_three = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g,
+        rx_four = /(?:^|:|,)(?:\s*\[)+/g,
+        rx_escapable = /[\\\"\u0000-\u001f\u007f-\u009f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
+        rx_dangerous = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
+
+    function f(n) {
+        // Format integers to have at least two digits.
+        return n < 10 
+            ? '0' + n 
+            : n;
+    }
+    
+    function this_value() {
+        return this.valueOf();
+    }
+
+    if (typeof Date.prototype.toJSON !== 'function') {
+
+        Date.prototype.toJSON = function () {
+
+            return isFinite(this.valueOf())
+                ? this.getUTCFullYear() + '-' +
+                        f(this.getUTCMonth() + 1) + '-' +
+                        f(this.getUTCDate()) + 'T' +
+                        f(this.getUTCHours()) + ':' +
+                        f(this.getUTCMinutes()) + ':' +
+                        f(this.getUTCSeconds()) + 'Z'
+                : null;
+        };
+
+        Boolean.prototype.toJSON = this_value;
+        Number.prototype.toJSON = this_value;
+        String.prototype.toJSON = this_value;
+    }
+
+    var gap,
+        indent,
+        meta,
+        rep;
+
+
+    function quote(string) {
+
+// If the string contains no control characters, no quote characters, and no
+// backslash characters, then we can safely slap some quotes around it.
+// Otherwise we must also replace the offending characters with safe escape
+// sequences.
+
+        rx_escapable.lastIndex = 0;
+        return rx_escapable.test(string) 
+            ? '"' + string.replace(rx_escapable, function (a) {
+                var c = meta[a];
+                return typeof c === 'string'
+                    ? c
+                    : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+            }) + '"' 
+            : '"' + string + '"';
+    }
+
+
+    function str(key, holder) {
+
+// Produce a string from holder[key].
+
+        var i,          // The loop counter.
+            k,          // The member key.
+            v,          // The member value.
+            length,
+            mind = gap,
+            partial,
+            value = holder[key];
+
+// If the value has a toJSON method, call it to obtain a replacement value.
+
+        if (value && typeof value === 'object' &&
+                typeof value.toJSON === 'function') {
+            value = value.toJSON(key);
+        }
+
+// If we were called with a replacer function, then call the replacer to
+// obtain a replacement value.
+
+        if (typeof rep === 'function') {
+            value = rep.call(holder, key, value);
+        }
+
+// What happens next depends on the value's type.
+
+        switch (typeof value) {
+        case 'string':
+            return quote(value);
+
+        case 'number':
+
+// JSON numbers must be finite. Encode non-finite numbers as null.
+
+            return isFinite(value) 
+                ? String(value) 
+                : 'null';
+
+        case 'boolean':
+        case 'null':
+
+// If the value is a boolean or null, convert it to a string. Note:
+// typeof null does not produce 'null'. The case is included here in
+// the remote chance that this gets fixed someday.
+
+            return String(value);
+
+// If the type is 'object', we might be dealing with an object or an array or
+// null.
+
+        case 'object':
+
+// Due to a specification blunder in ECMAScript, typeof null is 'object',
+// so watch out for that case.
+
+            if (!value) {
+                return 'null';
+            }
+
+// Make an array to hold the partial results of stringifying this object value.
+
+            gap += indent;
+            partial = [];
+
+// Is the value an array?
+
+            if (Object.prototype.toString.apply(value) === '[object Array]') {
+
+// The value is an array. Stringify every element. Use null as a placeholder
+// for non-JSON values.
+
+                length = value.length;
+                for (i = 0; i < length; i += 1) {
+                    partial[i] = str(i, value) || 'null';
+                }
+
+// Join all of the elements together, separated with commas, and wrap them in
+// brackets.
+
+                v = partial.length === 0
+                    ? '[]'
+                    : gap
+                        ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']'
+                        : '[' + partial.join(',') + ']';
+                gap = mind;
+                return v;
+            }
+
+// If the replacer is an array, use it to select the members to be stringified.
+
+            if (rep && typeof rep === 'object') {
+                length = rep.length;
+                for (i = 0; i < length; i += 1) {
+                    if (typeof rep[i] === 'string') {
+                        k = rep[i];
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (
+                                gap 
+                                    ? ': ' 
+                                    : ':'
+                            ) + v);
+                        }
+                    }
+                }
+            } else {
+
+// Otherwise, iterate through all of the keys in the object.
+
+                for (k in value) {
+                    if (Object.prototype.hasOwnProperty.call(value, k)) {
+                        v = str(k, value);
+                        if (v) {
+                            partial.push(quote(k) + (
+                                gap 
+                                    ? ': ' 
+                                    : ':'
+                            ) + v);
+                        }
+                    }
+                }
+            }
+
+// Join all of the member texts together, separated with commas,
+// and wrap them in braces.
+
+            v = partial.length === 0
+                ? '{}'
+                : gap
+                    ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}'
+                    : '{' + partial.join(',') + '}';
+            gap = mind;
+            return v;
+        }
+    }
+
+// If the JSON object does not yet have a stringify method, give it one.
+
+    if (typeof JSON.stringify !== 'function') {
+        meta = {    // table of character substitutions
+            '\b': '\\b',
+            '\t': '\\t',
+            '\n': '\\n',
+            '\f': '\\f',
+            '\r': '\\r',
+            '"': '\\"',
+            '\\': '\\\\'
+        };
+        JSON.stringify = function (value, replacer, space) {
+
+// The stringify method takes a value and an optional replacer, and an optional
+// space parameter, and returns a JSON text. The replacer can be a function
+// that can replace values, or an array of strings that will select the keys.
+// A default replacer method can be provided. Use of the space parameter can
+// produce text that is more easily readable.
+
+            var i;
+            gap = '';
+            indent = '';
+
+// If the space parameter is a number, make an indent string containing that
+// many spaces.
+
+            if (typeof space === 'number') {
+                for (i = 0; i < space; i += 1) {
+                    indent += ' ';
+                }
+
+// If the space parameter is a string, it will be used as the indent string.
+
+            } else if (typeof space === 'string') {
+                indent = space;
+            }
+
+// If there is a replacer, it must be a function or an array.
+// Otherwise, throw an error.
+
+            rep = replacer;
+            if (replacer && typeof replacer !== 'function' &&
+                    (typeof replacer !== 'object' ||
+                    typeof replacer.length !== 'number')) {
+                throw new Error('JSON.stringify');
+            }
+
+// Make a fake root object containing our value under the key of ''.
+// Return the result of stringifying the value.
+
+            return str('', {'': value});
+        };
+    }
+
+
+// If the JSON object does not yet have a parse method, give it one.
+
+    if (typeof JSON.parse !== 'function') {
+        JSON.parse = function (text, reviver) {
+
+// The parse method takes a text and an optional reviver function, and returns
+// a JavaScript value if the text is a valid JSON text.
+
+            var j;
+
+            function walk(holder, key) {
+
+// The walk method is used to recursively walk the resulting structure so
+// that modifications can be made.
+
+                var k, v, value = holder[key];
+                if (value && typeof value === 'object') {
+                    for (k in value) {
+                        if (Object.prototype.hasOwnProperty.call(value, k)) {
+                            v = walk(value, k);
+                            if (v !== undefined) {
+                                value[k] = v;
+                            } else {
+                                delete value[k];
+                            }
+                        }
+                    }
+                }
+                return reviver.call(holder, key, value);
+            }
+
+
+// Parsing happens in four stages. In the first stage, we replace certain
+// Unicode characters with escape sequences. JavaScript handles many characters
+// incorrectly, either silently deleting them, or treating them as line endings.
+
+            text = String(text);
+            rx_dangerous.lastIndex = 0;
+            if (rx_dangerous.test(text)) {
+                text = text.replace(rx_dangerous, function (a) {
+                    return '\\u' +
+                            ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
+                });
+            }
+
+// In the second stage, we run the text against regular expressions that look
+// for non-JSON patterns. We are especially concerned with '()' and 'new'
+// because they can cause invocation, and '=' because it can cause mutation.
+// But just to be safe, we want to reject all unexpected forms.
+
+// We split the second stage into 4 regexp operations in order to work around
+// crippling inefficiencies in IE's and Safari's regexp engines. First we
+// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
+// replace all simple value tokens with ']' characters. Third, we delete all
+// open brackets that follow a colon or comma or that begin the text. Finally,
+// we look to see that the remaining characters are only whitespace or ']' or
+// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
+
+            if (
+                rx_one.test(
+                    text
+                        .replace(rx_two, '@')
+                        .replace(rx_three, ']')
+                        .replace(rx_four, '')
+                )
+            ) {
+
+// In the third stage we use the eval function to compile the text into a
+// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
+// in JavaScript: it can begin a block or an object literal. We wrap the text
+// in parens to eliminate the ambiguity.
+
+                j = eval('(' + text + ')');
+
+// In the optional fourth stage, we recursively walk the new structure, passing
+// each name/value pair to a reviver function for possible transformation.
+
+                return typeof reviver === 'function'
+                    ? walk({'': j}, '')
+                    : j;
+            }
+
+// If the text is not JSON parseable, then a SyntaxError is thrown.
+
+            throw new SyntaxError('JSON.parse');
+        };
+    }
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/LICENSE b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/LICENSE
new file mode 100644
index 0000000..447239f
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/LICENSE
@@ -0,0 +1,23 @@
+Copyright (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative
+Reporters & Editors
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/arrays.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/arrays.js
new file mode 100644
index 0000000..748edea
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/arrays.js
@@ -0,0 +1,555 @@
+(function() {
+  var _ = typeof require == 'function' ? require('..') : window._;
+
+  QUnit.module('Arrays');
+
+  QUnit.test('first', function(assert) {
+    assert.equal(_.first([1, 2, 3]), 1, 'can pull out the first element of an array');
+    assert.equal(_([1, 2, 3]).first(), 1, 'can perform OO-style "first()"');
+    assert.deepEqual(_.first([1, 2, 3], 0), [], 'returns an empty array when n <= 0 (0 case)');
+    assert.deepEqual(_.first([1, 2, 3], -1), [], 'returns an empty array when n <= 0 (negative case)');
+    assert.deepEqual(_.first([1, 2, 3], 2), [1, 2], 'can fetch the first n elements');
+    assert.deepEqual(_.first([1, 2, 3], 5), [1, 2, 3], 'returns the whole array if n > length');
+    var result = (function(){ return _.first(arguments); }(4, 3, 2, 1));
+    assert.equal(result, 4, 'works on an arguments object');
+    result = _.map([[1, 2, 3], [1, 2, 3]], _.first);
+    assert.deepEqual(result, [1, 1], 'works well with _.map');
+    assert.equal(_.first(null), void 0, 'returns undefined when called on null');
+  });
+
+  QUnit.test('head', function(assert) {
+    assert.strictEqual(_.head, _.first, 'is an alias for first');
+  });
+
+  QUnit.test('take', function(assert) {
+    assert.strictEqual(_.take, _.first, 'is an alias for first');
+  });
+
+  QUnit.test('rest', function(assert) {
+    var numbers = [1, 2, 3, 4];
+    assert.deepEqual(_.rest(numbers), [2, 3, 4], 'fetches all but the first element');
+    assert.deepEqual(_.rest(numbers, 0), [1, 2, 3, 4], 'returns the whole array when index is 0');
+    assert.deepEqual(_.rest(numbers, 2), [3, 4], 'returns elements starting at the given index');
+    var result = (function(){ return _(arguments).rest(); }(1, 2, 3, 4));
+    assert.deepEqual(result, [2, 3, 4], 'works on an arguments object');
+    result = _.map([[1, 2, 3], [1, 2, 3]], _.rest);
+    assert.deepEqual(_.flatten(result), [2, 3, 2, 3], 'works well with _.map');
+  });
+
+  QUnit.test('tail', function(assert) {
+    assert.strictEqual(_.tail, _.rest, 'is an alias for rest');
+  });
+
+  QUnit.test('drop', function(assert) {
+    assert.strictEqual(_.drop, _.rest, 'is an alias for rest');
+  });
+
+  QUnit.test('initial', function(assert) {
+    assert.deepEqual(_.initial([1, 2, 3, 4, 5]), [1, 2, 3, 4], 'returns all but the last element');
+    assert.deepEqual(_.initial([1, 2, 3, 4], 2), [1, 2], 'returns all but the last n elements');
+    assert.deepEqual(_.initial([1, 2, 3, 4], 6), [], 'returns an empty array when n > length');
+    var result = (function(){ return _(arguments).initial(); }(1, 2, 3, 4));
+    assert.deepEqual(result, [1, 2, 3], 'works on an arguments object');
+    result = _.map([[1, 2, 3], [1, 2, 3]], _.initial);
+    assert.deepEqual(_.flatten(result), [1, 2, 1, 2], 'works well with _.map');
+  });
+
+  QUnit.test('last', function(assert) {
+    assert.equal(_.last([1, 2, 3]), 3, 'can pull out the last element of an array');
+    assert.equal(_([1, 2, 3]).last(), 3, 'can perform OO-style "last()"');
+    assert.deepEqual(_.last([1, 2, 3], 0), [], 'returns an empty array when n <= 0 (0 case)');
+    assert.deepEqual(_.last([1, 2, 3], -1), [], 'returns an empty array when n <= 0 (negative case)');
+    assert.deepEqual(_.last([1, 2, 3], 2), [2, 3], 'can fetch the last n elements');
+    assert.deepEqual(_.last([1, 2, 3], 5), [1, 2, 3], 'returns the whole array if n > length');
+    var result = (function(){ return _(arguments).last(); }(1, 2, 3, 4));
+    assert.equal(result, 4, 'works on an arguments object');
+    result = _.map([[1, 2, 3], [1, 2, 3]], _.last);
+    assert.deepEqual(result, [3, 3], 'works well with _.map');
+    assert.equal(_.last(null), void 0, 'returns undefined when called on null');
+  });
+
+  QUnit.test('compact', function(assert) {
+    assert.deepEqual(_.compact([1, false, null, 0, '', void 0, NaN, 2]), [1, 2], 'removes all falsy values');
+    var result = (function(){ return _.compact(arguments); }(0, 1, false, 2, false, 3));
+    assert.deepEqual(result, [1, 2, 3], 'works on an arguments object');
+    result = _.map([[1, false, false], [false, false, 3]], _.compact);
+    assert.deepEqual(result, [[1], [3]], 'works well with _.map');
+  });
+
+  QUnit.test('flatten', function(assert) {
+    assert.deepEqual(_.flatten(null), [], 'supports null');
+    assert.deepEqual(_.flatten(void 0), [], 'supports undefined');
+
+    assert.deepEqual(_.flatten([[], [[]], []]), [], 'supports empty arrays');
+    assert.deepEqual(_.flatten([[], [[]], []], true), [[]], 'can shallowly flatten empty arrays');
+
+    var list = [1, [2], [3, [[[4]]]]];
+    assert.deepEqual(_.flatten(list), [1, 2, 3, 4], 'can flatten nested arrays');
+    assert.deepEqual(_.flatten(list, true), [1, 2, 3, [[[4]]]], 'can shallowly flatten nested arrays');
+    var result = (function(){ return _.flatten(arguments); }(1, [2], [3, [[[4]]]]));
+    assert.deepEqual(result, [1, 2, 3, 4], 'works on an arguments object');
+    list = [[1], [2], [3], [[4]]];
+    assert.deepEqual(_.flatten(list, true), [1, 2, 3, [4]], 'can shallowly flatten arrays containing only other arrays');
+
+    assert.equal(_.flatten([_.range(10), _.range(10), 5, 1, 3], true).length, 23, 'can flatten medium length arrays');
+    assert.equal(_.flatten([_.range(10), _.range(10), 5, 1, 3]).length, 23, 'can shallowly flatten medium length arrays');
+    assert.equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3]).length, 1056003, 'can handle massive arrays');
+    assert.equal(_.flatten([new Array(1000000), _.range(56000), 5, 1, 3], true).length, 1056003, 'can handle massive arrays in shallow mode');
+
+    var x = _.range(100000);
+    for (var i = 0; i < 1000; i++) x = [x];
+    assert.deepEqual(_.flatten(x), _.range(100000), 'can handle very deep arrays');
+    assert.deepEqual(_.flatten(x, true), x[0], 'can handle very deep arrays in shallow mode');
+  });
+
+  QUnit.test('without', function(assert) {
+    var list = [1, 2, 1, 0, 3, 1, 4];
+    assert.deepEqual(_.without(list, 0, 1), [2, 3, 4], 'removes all instances of the given values');
+    var result = (function(){ return _.without(arguments, 0, 1); }(1, 2, 1, 0, 3, 1, 4));
+    assert.deepEqual(result, [2, 3, 4], 'works on an arguments object');
+
+    list = [{one: 1}, {two: 2}];
+    assert.deepEqual(_.without(list, {one: 1}), list, 'compares objects by reference (value case)');
+    assert.deepEqual(_.without(list, list[0]), [{two: 2}], 'compares objects by reference (reference case)');
+  });
+
+  QUnit.test('sortedIndex', function(assert) {
+    var numbers = [10, 20, 30, 40, 50];
+    var indexFor35 = _.sortedIndex(numbers, 35);
+    assert.equal(indexFor35, 3, 'finds the index at which a value should be inserted to retain order');
+    var indexFor30 = _.sortedIndex(numbers, 30);
+    assert.equal(indexFor30, 2, 'finds the smallest index at which a value could be inserted to retain order');
+
+    var objects = [{x: 10}, {x: 20}, {x: 30}, {x: 40}];
+    var iterator = function(obj){ return obj.x; };
+    assert.strictEqual(_.sortedIndex(objects, {x: 25}, iterator), 2, 'uses the result of `iterator` for order comparisons');
+    assert.strictEqual(_.sortedIndex(objects, {x: 35}, 'x'), 3, 'when `iterator` is a string, uses that key for order comparisons');
+
+    var context = {1: 2, 2: 3, 3: 4};
+    iterator = function(obj){ return this[obj]; };
+    assert.strictEqual(_.sortedIndex([1, 3], 2, iterator, context), 1, 'can execute its iterator in the given context');
+
+    var values = [0, 1, 3, 7, 15, 31, 63, 127, 255, 511, 1023, 2047, 4095, 8191, 16383, 32767, 65535, 131071, 262143, 524287,
+        1048575, 2097151, 4194303, 8388607, 16777215, 33554431, 67108863, 134217727, 268435455, 536870911, 1073741823, 2147483647];
+    var largeArray = Array(Math.pow(2, 32) - 1);
+    var length = values.length;
+    // Sparsely populate `array`
+    while (length--) {
+      largeArray[values[length]] = values[length];
+    }
+    assert.equal(_.sortedIndex(largeArray, 2147483648), 2147483648, 'works with large indexes');
+  });
+
+  QUnit.test('uniq', function(assert) {
+    var list = [1, 2, 1, 3, 1, 4];
+    assert.deepEqual(_.uniq(list), [1, 2, 3, 4], 'can find the unique values of an unsorted array');
+    list = [1, 1, 1, 2, 2, 3];
+    assert.deepEqual(_.uniq(list, true), [1, 2, 3], 'can find the unique values of a sorted array faster');
+
+    list = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}, {name: 'Curly'}];
+    var expected = [{name: 'Moe'}, {name: 'Curly'}, {name: 'Larry'}];
+    var iterator = function(stooge) { return stooge.name; };
+    assert.deepEqual(_.uniq(list, false, iterator), expected, 'uses the result of `iterator` for uniqueness comparisons (unsorted case)');
+    assert.deepEqual(_.uniq(list, iterator), expected, '`sorted` argument defaults to false when omitted');
+    assert.deepEqual(_.uniq(list, 'name'), expected, 'when `iterator` is a string, uses that key for comparisons (unsorted case)');
+
+    list = [{score: 8}, {score: 10}, {score: 10}];
+    expected = [{score: 8}, {score: 10}];
+    iterator = function(item) { return item.score; };
+    assert.deepEqual(_.uniq(list, true, iterator), expected, 'uses the result of `iterator` for uniqueness comparisons (sorted case)');
+    assert.deepEqual(_.uniq(list, true, 'score'), expected, 'when `iterator` is a string, uses that key for comparisons (sorted case)');
+
+    assert.deepEqual(_.uniq([{0: 1}, {0: 1}, {0: 1}, {0: 2}], 0), [{0: 1}, {0: 2}], 'can use falsey pluck like iterator');
+
+    var result = (function(){ return _.uniq(arguments); }(1, 2, 1, 3, 1, 4));
+    assert.deepEqual(result, [1, 2, 3, 4], 'works on an arguments object');
+
+    var a = {}, b = {}, c = {};
+    assert.deepEqual(_.uniq([a, b, a, b, c]), [a, b, c], 'works on values that can be tested for equivalency but not ordered');
+
+    assert.deepEqual(_.uniq(null), [], 'returns an empty array when `array` is not iterable');
+
+    var context = {};
+    list = [3];
+    _.uniq(list, function(value, index, array) {
+      assert.strictEqual(this, context, 'executes its iterator in the given context');
+      assert.strictEqual(value, 3, 'passes its iterator the value');
+      assert.strictEqual(index, 0, 'passes its iterator the index');
+      assert.strictEqual(array, list, 'passes its iterator the entire array');
+    }, context);
+
+  });
+
+  QUnit.test('unique', function(assert) {
+    assert.strictEqual(_.unique, _.uniq, 'is an alias for uniq');
+  });
+
+  QUnit.test('intersection', function(assert) {
+    var stooges = ['moe', 'curly', 'larry'], leaders = ['moe', 'groucho'];
+    assert.deepEqual(_.intersection(stooges, leaders), ['moe'], 'can find the set intersection of two arrays');
+    assert.deepEqual(_(stooges).intersection(leaders), ['moe'], 'can perform an OO-style intersection');
+    var result = (function(){ return _.intersection(arguments, leaders); }('moe', 'curly', 'larry'));
+    assert.deepEqual(result, ['moe'], 'works on an arguments object');
+    var theSixStooges = ['moe', 'moe', 'curly', 'curly', 'larry', 'larry'];
+    assert.deepEqual(_.intersection(theSixStooges, leaders), ['moe'], 'returns a duplicate-free array');
+    result = _.intersection([2, 4, 3, 1], [1, 2, 3]);
+    assert.deepEqual(result, [2, 3, 1], 'preserves the order of the first array');
+    result = _.intersection(null, [1, 2, 3]);
+    assert.deepEqual(result, [], 'returns an empty array when passed null as the first argument');
+    result = _.intersection([1, 2, 3], null);
+    assert.deepEqual(result, [], 'returns an empty array when passed null as an argument beyond the first');
+  });
+
+  QUnit.test('union', function(assert) {
+    var result = _.union([1, 2, 3], [2, 30, 1], [1, 40]);
+    assert.deepEqual(result, [1, 2, 3, 30, 40], 'can find the union of a list of arrays');
+
+    result = _([1, 2, 3]).union([2, 30, 1], [1, 40]);
+    assert.deepEqual(result, [1, 2, 3, 30, 40], 'can perform an OO-style union');
+
+    result = _.union([1, 2, 3], [2, 30, 1], [1, 40, [1]]);
+    assert.deepEqual(result, [1, 2, 3, 30, 40, [1]], 'can find the union of a list of nested arrays');
+
+    result = _.union([10, 20], [1, 30, 10], [0, 40]);
+    assert.deepEqual(result, [10, 20, 1, 30, 0, 40], 'orders values by their first encounter');
+
+    result = (function(){ return _.union(arguments, [2, 30, 1], [1, 40]); }(1, 2, 3));
+    assert.deepEqual(result, [1, 2, 3, 30, 40], 'works on an arguments object');
+
+    assert.deepEqual(_.union([1, 2, 3], 4), [1, 2, 3], 'restricts the union to arrays only');
+  });
+
+  QUnit.test('difference', function(assert) {
+    var result = _.difference([1, 2, 3], [2, 30, 40]);
+    assert.deepEqual(result, [1, 3], 'can find the difference of two arrays');
+
+    result = _([1, 2, 3]).difference([2, 30, 40]);
+    assert.deepEqual(result, [1, 3], 'can perform an OO-style difference');
+
+    result = _.difference([1, 2, 3, 4], [2, 30, 40], [1, 11, 111]);
+    assert.deepEqual(result, [3, 4], 'can find the difference of three arrays');
+
+    result = _.difference([8, 9, 3, 1], [3, 8]);
+    assert.deepEqual(result, [9, 1], 'preserves the order of the first array');
+
+    result = (function(){ return _.difference(arguments, [2, 30, 40]); }(1, 2, 3));
+    assert.deepEqual(result, [1, 3], 'works on an arguments object');
+
+    result = _.difference([1, 2, 3], 1);
+    assert.deepEqual(result, [1, 2, 3], 'restrict the difference to arrays only');
+  });
+
+  QUnit.test('zip', function(assert) {
+    var names = ['moe', 'larry', 'curly'], ages = [30, 40, 50], leaders = [true];
+    assert.deepEqual(_.zip(names, ages, leaders), [
+      ['moe', 30, true],
+      ['larry', 40, void 0],
+      ['curly', 50, void 0]
+    ], 'zipped together arrays of different lengths');
+
+    var stooges = _.zip(['moe', 30, 'stooge 1'], ['larry', 40, 'stooge 2'], ['curly', 50, 'stooge 3']);
+    assert.deepEqual(stooges, [['moe', 'larry', 'curly'], [30, 40, 50], ['stooge 1', 'stooge 2', 'stooge 3']], 'zipped pairs');
+
+    // In the case of different lengths of the tuples, undefined values
+    // should be used as placeholder
+    stooges = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']);
+    assert.deepEqual(stooges, [['moe', 'larry', 'curly'], [30, 40, 50], [void 0, void 0, 'extra data']], 'zipped pairs with empties');
+
+    var empty = _.zip([]);
+    assert.deepEqual(empty, [], 'unzipped empty');
+
+    assert.deepEqual(_.zip(null), [], 'handles null');
+    assert.deepEqual(_.zip(), [], '_.zip() returns []');
+  });
+
+  QUnit.test('unzip', function(assert) {
+    assert.deepEqual(_.unzip(null), [], 'handles null');
+
+    assert.deepEqual(_.unzip([['a', 'b'], [1, 2]]), [['a', 1], ['b', 2]]);
+
+    // complements zip
+    var zipped = _.zip(['fred', 'barney'], [30, 40], [true, false]);
+    assert.deepEqual(_.unzip(zipped), [['fred', 'barney'], [30, 40], [true, false]]);
+
+    zipped = _.zip(['moe', 30], ['larry', 40], ['curly', 50, 'extra data']);
+    assert.deepEqual(_.unzip(zipped), [['moe', 30, void 0], ['larry', 40, void 0], ['curly', 50, 'extra data']], 'Uses length of largest array');
+  });
+
+  QUnit.test('object', function(assert) {
+    var result = _.object(['moe', 'larry', 'curly'], [30, 40, 50]);
+    var shouldBe = {moe: 30, larry: 40, curly: 50};
+    assert.deepEqual(result, shouldBe, 'two arrays zipped together into an object');
+
+    result = _.object([['one', 1], ['two', 2], ['three', 3]]);
+    shouldBe = {one: 1, two: 2, three: 3};
+    assert.deepEqual(result, shouldBe, 'an array of pairs zipped together into an object');
+
+    var stooges = {moe: 30, larry: 40, curly: 50};
+    assert.deepEqual(_.object(_.pairs(stooges)), stooges, 'an object converted to pairs and back to an object');
+
+    assert.deepEqual(_.object(null), {}, 'handles nulls');
+  });
+
+  QUnit.test('indexOf', function(assert) {
+    var numbers = [1, 2, 3];
+    assert.equal(_.indexOf(numbers, 2), 1, 'can compute indexOf');
+    var result = (function(){ return _.indexOf(arguments, 2); }(1, 2, 3));
+    assert.equal(result, 1, 'works on an arguments object');
+
+    _.each([null, void 0, [], false], function(val) {
+      var msg = 'Handles: ' + (_.isArray(val) ? '[]' : val);
+      assert.equal(_.indexOf(val, 2), -1, msg);
+      assert.equal(_.indexOf(val, 2, -1), -1, msg);
+      assert.equal(_.indexOf(val, 2, -20), -1, msg);
+      assert.equal(_.indexOf(val, 2, 15), -1, msg);
+    });
+
+    var num = 35;
+    numbers = [10, 20, 30, 40, 50];
+    var index = _.indexOf(numbers, num, true);
+    assert.equal(index, -1, '35 is not in the list');
+
+    numbers = [10, 20, 30, 40, 50]; num = 40;
+    index = _.indexOf(numbers, num, true);
+    assert.equal(index, 3, '40 is in the list');
+
+    numbers = [1, 40, 40, 40, 40, 40, 40, 40, 50, 60, 70]; num = 40;
+    assert.equal(_.indexOf(numbers, num, true), 1, '40 is in the list');
+    assert.equal(_.indexOf(numbers, 6, true), -1, '6 isnt in the list');
+    assert.equal(_.indexOf([1, 2, 5, 4, 6, 7], 5, true), -1, 'sorted indexOf doesn\'t uses binary search');
+    assert.ok(_.every(['1', [], {}, null], function() {
+      return _.indexOf(numbers, num, {}) === 1;
+    }), 'non-nums as fromIndex make indexOf assume sorted');
+
+    numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
+    index = _.indexOf(numbers, 2, 5);
+    assert.equal(index, 7, 'supports the fromIndex argument');
+
+    index = _.indexOf([,,, 0], void 0);
+    assert.equal(index, 0, 'treats sparse arrays as if they were dense');
+
+    var array = [1, 2, 3, 1, 2, 3];
+    assert.strictEqual(_.indexOf(array, 1, -3), 3, 'neg `fromIndex` starts at the right index');
+    assert.strictEqual(_.indexOf(array, 1, -2), -1, 'neg `fromIndex` starts at the right index');
+    assert.strictEqual(_.indexOf(array, 2, -3), 4);
+    _.each([-6, -8, -Infinity], function(fromIndex) {
+      assert.strictEqual(_.indexOf(array, 1, fromIndex), 0);
+    });
+    assert.strictEqual(_.indexOf([1, 2, 3], 1, true), 0);
+
+    index = _.indexOf([], void 0, true);
+    assert.equal(index, -1, 'empty array with truthy `isSorted` returns -1');
+  });
+
+  QUnit.test('indexOf with NaN', function(assert) {
+    assert.strictEqual(_.indexOf([1, 2, NaN, NaN], NaN), 2, 'Expected [1, 2, NaN] to contain NaN');
+    assert.strictEqual(_.indexOf([1, 2, Infinity], NaN), -1, 'Expected [1, 2, NaN] to contain NaN');
+
+    assert.strictEqual(_.indexOf([1, 2, NaN, NaN], NaN, 1), 2, 'startIndex does not affect result');
+    assert.strictEqual(_.indexOf([1, 2, NaN, NaN], NaN, -2), 2, 'startIndex does not affect result');
+
+    (function() {
+      assert.strictEqual(_.indexOf(arguments, NaN), 2, 'Expected arguments [1, 2, NaN] to contain NaN');
+    }(1, 2, NaN, NaN));
+  });
+
+  QUnit.test('indexOf with +- 0', function(assert) {
+    _.each([-0, +0], function(val) {
+      assert.strictEqual(_.indexOf([1, 2, val, val], val), 2);
+      assert.strictEqual(_.indexOf([1, 2, val, val], -val), 2);
+    });
+  });
+
+  QUnit.test('lastIndexOf', function(assert) {
+    var numbers = [1, 0, 1];
+    var falsey = [void 0, '', 0, false, NaN, null, void 0];
+    assert.equal(_.lastIndexOf(numbers, 1), 2);
+
+    numbers = [1, 0, 1, 0, 0, 1, 0, 0, 0];
+    numbers.lastIndexOf = null;
+    assert.equal(_.lastIndexOf(numbers, 1), 5, 'can compute lastIndexOf, even without the native function');
+    assert.equal(_.lastIndexOf(numbers, 0), 8, 'lastIndexOf the other element');
+    var result = (function(){ return _.lastIndexOf(arguments, 1); }(1, 0, 1, 0, 0, 1, 0, 0, 0));
+    assert.equal(result, 5, 'works on an arguments object');
+
+    _.each([null, void 0, [], false], function(val) {
+      var msg = 'Handles: ' + (_.isArray(val) ? '[]' : val);
+      assert.equal(_.lastIndexOf(val, 2), -1, msg);
+      assert.equal(_.lastIndexOf(val, 2, -1), -1, msg);
+      assert.equal(_.lastIndexOf(val, 2, -20), -1, msg);
+      assert.equal(_.lastIndexOf(val, 2, 15), -1, msg);
+    });
+
+    numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
+    var index = _.lastIndexOf(numbers, 2, 2);
+    assert.equal(index, 1, 'supports the fromIndex argument');
+
+    var array = [1, 2, 3, 1, 2, 3];
+
+    assert.strictEqual(_.lastIndexOf(array, 1, 0), 0, 'starts at the correct from idx');
+    assert.strictEqual(_.lastIndexOf(array, 3), 5, 'should return the index of the last matched value');
+    assert.strictEqual(_.lastIndexOf(array, 4), -1, 'should return `-1` for an unmatched value');
+
+    assert.strictEqual(_.lastIndexOf(array, 1, 2), 0, 'should work with a positive `fromIndex`');
+
+    _.each([6, 8, Math.pow(2, 32), Infinity], function(fromIndex) {
+      assert.strictEqual(_.lastIndexOf(array, void 0, fromIndex), -1);
+      assert.strictEqual(_.lastIndexOf(array, 1, fromIndex), 3);
+      assert.strictEqual(_.lastIndexOf(array, '', fromIndex), -1);
+    });
+
+    var expected = _.map(falsey, function(value) {
+      return typeof value == 'number' ? -1 : 5;
+    });
+
+    var actual = _.map(falsey, function(fromIndex) {
+      return _.lastIndexOf(array, 3, fromIndex);
+    });
+
+    assert.deepEqual(actual, expected, 'should treat falsey `fromIndex` values, except `0` and `NaN`, as `array.length`');
+    assert.strictEqual(_.lastIndexOf(array, 3, '1'), 5, 'should treat non-number `fromIndex` values as `array.length`');
+    assert.strictEqual(_.lastIndexOf(array, 3, true), 5, 'should treat non-number `fromIndex` values as `array.length`');
+
+    assert.strictEqual(_.lastIndexOf(array, 2, -3), 1, 'should work with a negative `fromIndex`');
+    assert.strictEqual(_.lastIndexOf(array, 1, -3), 3, 'neg `fromIndex` starts at the right index');
+
+    assert.deepEqual(_.map([-6, -8, -Infinity], function(fromIndex) {
+      return _.lastIndexOf(array, 1, fromIndex);
+    }), [0, -1, -1]);
+  });
+
+  QUnit.test('lastIndexOf with NaN', function(assert) {
+    assert.strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN), 3, 'Expected [1, 2, NaN] to contain NaN');
+    assert.strictEqual(_.lastIndexOf([1, 2, Infinity], NaN), -1, 'Expected [1, 2, NaN] to contain NaN');
+
+    assert.strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN, 2), 2, 'fromIndex does not affect result');
+    assert.strictEqual(_.lastIndexOf([1, 2, NaN, NaN], NaN, -2), 2, 'fromIndex does not affect result');
+
+    (function() {
+      assert.strictEqual(_.lastIndexOf(arguments, NaN), 3, 'Expected arguments [1, 2, NaN] to contain NaN');
+    }(1, 2, NaN, NaN));
+  });
+
+  QUnit.test('lastIndexOf with +- 0', function(assert) {
+    _.each([-0, +0], function(val) {
+      assert.strictEqual(_.lastIndexOf([1, 2, val, val], val), 3);
+      assert.strictEqual(_.lastIndexOf([1, 2, val, val], -val), 3);
+      assert.strictEqual(_.lastIndexOf([-1, 1, 2], -val), -1);
+    });
+  });
+
+  QUnit.test('findIndex', function(assert) {
+    var objects = [
+      {a: 0, b: 0},
+      {a: 1, b: 1},
+      {a: 2, b: 2},
+      {a: 0, b: 0}
+    ];
+
+    assert.equal(_.findIndex(objects, function(obj) {
+      return obj.a === 0;
+    }), 0);
+
+    assert.equal(_.findIndex(objects, function(obj) {
+      return obj.b * obj.a === 4;
+    }), 2);
+
+    assert.equal(_.findIndex(objects, 'a'), 1, 'Uses lookupIterator');
+
+    assert.equal(_.findIndex(objects, function(obj) {
+      return obj.b * obj.a === 5;
+    }), -1);
+
+    assert.equal(_.findIndex(null, _.noop), -1);
+    assert.strictEqual(_.findIndex(objects, function(a) {
+      return a.foo === null;
+    }), -1);
+    _.findIndex([{a: 1}], function(a, key, obj) {
+      assert.equal(key, 0);
+      assert.deepEqual(obj, [{a: 1}]);
+      assert.strictEqual(this, objects, 'called with context');
+    }, objects);
+
+    var sparse = [];
+    sparse[20] = {a: 2, b: 2};
+    assert.equal(_.findIndex(sparse, function(obj) {
+      return obj && obj.b * obj.a === 4;
+    }), 20, 'Works with sparse arrays');
+
+    var array = [1, 2, 3, 4];
+    array.match = 55;
+    assert.strictEqual(_.findIndex(array, function(x) { return x === 55; }), -1, 'doesn\'t match array-likes keys');
+  });
+
+  QUnit.test('findLastIndex', function(assert) {
+    var objects = [
+      {a: 0, b: 0},
+      {a: 1, b: 1},
+      {a: 2, b: 2},
+      {a: 0, b: 0}
+    ];
+
+    assert.equal(_.findLastIndex(objects, function(obj) {
+      return obj.a === 0;
+    }), 3);
+
+    assert.equal(_.findLastIndex(objects, function(obj) {
+      return obj.b * obj.a === 4;
+    }), 2);
+
+    assert.equal(_.findLastIndex(objects, 'a'), 2, 'Uses lookupIterator');
+
+    assert.equal(_.findLastIndex(objects, function(obj) {
+      return obj.b * obj.a === 5;
+    }), -1);
+
+    assert.equal(_.findLastIndex(null, _.noop), -1);
+    assert.strictEqual(_.findLastIndex(objects, function(a) {
+      return a.foo === null;
+    }), -1);
+    _.findLastIndex([{a: 1}], function(a, key, obj) {
+      assert.equal(key, 0);
+      assert.deepEqual(obj, [{a: 1}]);
+      assert.strictEqual(this, objects, 'called with context');
+    }, objects);
+
+    var sparse = [];
+    sparse[20] = {a: 2, b: 2};
+    assert.equal(_.findLastIndex(sparse, function(obj) {
+      return obj && obj.b * obj.a === 4;
+    }), 20, 'Works with sparse arrays');
+
+    var array = [1, 2, 3, 4];
+    array.match = 55;
+    assert.strictEqual(_.findLastIndex(array, function(x) { return x === 55; }), -1, 'doesn\'t match array-likes keys');
+  });
+
+  QUnit.test('range', function(assert) {
+    assert.deepEqual(_.range(0), [], 'range with 0 as a first argument generates an empty array');
+    assert.deepEqual(_.range(4), [0, 1, 2, 3], 'range with a single positive argument generates an array of elements 0,1,2,...,n-1');
+    assert.deepEqual(_.range(5, 8), [5, 6, 7], 'range with two arguments a &amp; b, a&lt;b generates an array of elements a,a+1,a+2,...,b-2,b-1');
+    assert.deepEqual(_.range(3, 10, 3), [3, 6, 9], 'range with three arguments a &amp; b &amp; c, c &lt; b-a, a &lt; b generates an array of elements a,a+c,a+2c,...,b - (multiplier of a) &lt; c');
+    assert.deepEqual(_.range(3, 10, 15), [3], 'range with three arguments a &amp; b &amp; c, c &gt; b-a, a &lt; b generates an array with a single element, equal to a');
+    assert.deepEqual(_.range(12, 7, -2), [12, 10, 8], 'range with three arguments a &amp; b &amp; c, a &gt; b, c &lt; 0 generates an array of elements a,a-c,a-2c and ends with the number not less than b');
+    assert.deepEqual(_.range(0, -10, -1), [0, -1, -2, -3, -4, -5, -6, -7, -8, -9], 'final example in the Python docs');
+    assert.strictEqual(1 / _.range(-0, 1)[0], -Infinity, 'should preserve -0');
+    assert.deepEqual(_.range(8, 5), [8, 7, 6], 'negative range generates descending array');
+    assert.deepEqual(_.range(-3), [0, -1, -2], 'negative range generates descending array');
+  });
+
+  QUnit.test('chunk', function(assert) {
+    assert.deepEqual(_.chunk([], 2), [], 'chunk for empty array returns an empty array');
+
+    assert.deepEqual(_.chunk([1, 2, 3], 0), [], 'chunk into parts of 0 elements returns empty array');
+    assert.deepEqual(_.chunk([1, 2, 3], -1), [], 'chunk into parts of negative amount of elements returns an empty array');
+    assert.deepEqual(_.chunk([1, 2, 3]), [], 'defaults to empty array (chunk size 0)');
+
+    assert.deepEqual(_.chunk([1, 2, 3], 1), [[1], [2], [3]], 'chunk into parts of 1 elements returns original array');
+
+    assert.deepEqual(_.chunk([1, 2, 3], 3), [[1, 2, 3]], 'chunk into parts of current array length elements returns the original array');
+    assert.deepEqual(_.chunk([1, 2, 3], 5), [[1, 2, 3]], 'chunk into parts of more then current array length elements returns the original array');
+
+    assert.deepEqual(_.chunk([10, 20, 30, 40, 50, 60, 70], 2), [[10, 20], [30, 40], [50, 60], [70]], 'chunk into parts of less then current array length elements');
+    assert.deepEqual(_.chunk([10, 20, 30, 40, 50, 60, 70], 3), [[10, 20, 30], [40, 50, 60], [70]], 'chunk into parts of less then current array length elements');
+  });
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/chaining.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/chaining.js
new file mode 100644
index 0000000..6ad21dc
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/chaining.js
@@ -0,0 +1,99 @@
+(function() {
+  var _ = typeof require == 'function' ? require('..') : window._;
+
+  QUnit.module('Chaining');
+
+  QUnit.test('map/flatten/reduce', function(assert) {
+    var lyrics = [
+      'I\'m a lumberjack and I\'m okay',
+      'I sleep all night and I work all day',
+      'He\'s a lumberjack and he\'s okay',
+      'He sleeps all night and he works all day'
+    ];
+    var counts = _(lyrics).chain()
+      .map(function(line) { return line.split(''); })
+      .flatten()
+      .reduce(function(hash, l) {
+        hash[l] = hash[l] || 0;
+        hash[l]++;
+        return hash;
+      }, {})
+      .value();
+    assert.equal(counts.a, 16, 'counted all the letters in the song');
+    assert.equal(counts.e, 10, 'counted all the letters in the song');
+  });
+
+  QUnit.test('select/reject/sortBy', function(assert) {
+    var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    numbers = _(numbers).chain().select(function(n) {
+      return n % 2 === 0;
+    }).reject(function(n) {
+      return n % 4 === 0;
+    }).sortBy(function(n) {
+      return -n;
+    }).value();
+    assert.deepEqual(numbers, [10, 6, 2], 'filtered and reversed the numbers');
+  });
+
+  QUnit.test('select/reject/sortBy in functional style', function(assert) {
+    var numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
+    numbers = _.chain(numbers).select(function(n) {
+      return n % 2 === 0;
+    }).reject(function(n) {
+      return n % 4 === 0;
+    }).sortBy(function(n) {
+      return -n;
+    }).value();
+    assert.deepEqual(numbers, [10, 6, 2], 'filtered and reversed the numbers');
+  });
+
+  QUnit.test('reverse/concat/unshift/pop/map', function(assert) {
+    var numbers = [1, 2, 3, 4, 5];
+    numbers = _(numbers).chain()
+      .reverse()
+      .concat([5, 5, 5])
+      .unshift(17)
+      .pop()
+      .map(function(n){ return n * 2; })
+      .value();
+    assert.deepEqual(numbers, [34, 10, 8, 6, 4, 2, 10, 10], 'can chain together array functions.');
+  });
+
+  QUnit.test('splice', function(assert) {
+    var instance = _([1, 2, 3, 4, 5]).chain();
+    assert.deepEqual(instance.splice(1, 3).value(), [1, 5]);
+    assert.deepEqual(instance.splice(1, 0).value(), [1, 5]);
+    assert.deepEqual(instance.splice(1, 1).value(), [1]);
+    assert.deepEqual(instance.splice(0, 1).value(), [], '#397 Can create empty array');
+  });
+
+  QUnit.test('shift', function(assert) {
+    var instance = _([1, 2, 3]).chain();
+    assert.deepEqual(instance.shift().value(), [2, 3]);
+    assert.deepEqual(instance.shift().value(), [3]);
+    assert.deepEqual(instance.shift().value(), [], '#397 Can create empty array');
+  });
+
+  QUnit.test('pop', function(assert) {
+    var instance = _([1, 2, 3]).chain();
+    assert.deepEqual(instance.pop().value(), [1, 2]);
+    assert.deepEqual(instance.pop().value(), [1]);
+    assert.deepEqual(instance.pop().value(), [], '#397 Can create empty array');
+  });
+
+  QUnit.test('chaining works in small stages', function(assert) {
+    var o = _([1, 2, 3, 4]).chain();
+    assert.deepEqual(o.filter(function(i) { return i < 3; }).value(), [1, 2]);
+    assert.deepEqual(o.filter(function(i) { return i > 2; }).value(), [3, 4]);
+  });
+
+  QUnit.test('#1562: Engine proxies for chained functions', function(assert) {
+    var wrapped = _(512);
+    assert.strictEqual(wrapped.toJSON(), 512);
+    assert.strictEqual(wrapped.valueOf(), 512);
+    assert.strictEqual(+wrapped, 512);
+    assert.strictEqual(wrapped.toString(), '512');
+    assert.strictEqual('' + wrapped, '512');
+  });
+
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/collections.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/collections.js
new file mode 100644
index 0000000..182f7a2
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/collections.js
@@ -0,0 +1,896 @@
+(function() {
+  var _ = typeof require == 'function' ? require('..') : window._;
+
+  QUnit.module('Collections');
+
+  QUnit.test('each', function(assert) {
+    _.each([1, 2, 3], function(num, i) {
+      assert.equal(num, i + 1, 'each iterators provide value and iteration count');
+    });
+
+    var answers = [];
+    _.each([1, 2, 3], function(num){ answers.push(num * this.multiplier); }, {multiplier: 5});
+    assert.deepEqual(answers, [5, 10, 15], 'context object property accessed');
+
+    answers = [];
+    _.each([1, 2, 3], function(num){ answers.push(num); });
+    assert.deepEqual(answers, [1, 2, 3], 'can iterate a simple array');
+
+    answers = [];
+    var obj = {one: 1, two: 2, three: 3};
+    obj.constructor.prototype.four = 4;
+    _.each(obj, function(value, key){ answers.push(key); });
+    assert.deepEqual(answers, ['one', 'two', 'three'], 'iterating over objects works, and ignores the object prototype.');
+    delete obj.constructor.prototype.four;
+
+    // ensure the each function is JITed
+    _(1000).times(function() { _.each([], function(){}); });
+    var count = 0;
+    obj = {1: 'foo', 2: 'bar', 3: 'baz'};
+    _.each(obj, function(){ count++; });
+    assert.equal(count, 3, 'the fun should be called only 3 times');
+
+    var answer = null;
+    _.each([1, 2, 3], function(num, index, arr){ if (_.include(arr, num)) answer = true; });
+    assert.ok(answer, 'can reference the original collection from inside the iterator');
+
+    answers = 0;
+    _.each(null, function(){ ++answers; });
+    assert.equal(answers, 0, 'handles a null properly');
+
+    _.each(false, function(){});
+
+    var a = [1, 2, 3];
+    assert.strictEqual(_.each(a, function(){}), a);
+    assert.strictEqual(_.each(null, function(){}), null);
+  });
+
+  QUnit.test('forEach', function(assert) {
+    assert.strictEqual(_.forEach, _.each, 'is an alias for each');
+  });
+
+  QUnit.test('lookupIterator with contexts', function(assert) {
+    _.each([true, false, 'yes', '', 0, 1, {}], function(context) {
+      _.each([1], function() {
+        assert.equal(this, context);
+      }, context);
+    });
+  });
+
+  QUnit.test('Iterating objects with sketchy length properties', function(assert) {
+    var functions = [
+      'each', 'map', 'filter', 'find',
+      'some', 'every', 'max', 'min',
+      'groupBy', 'countBy', 'partition', 'indexBy'
+    ];
+    var reducers = ['reduce', 'reduceRight'];
+
+    var tricks = [
+      {length: '5'},
+      {length: {valueOf: _.constant(5)}},
+      {length: Math.pow(2, 53) + 1},
+      {length: Math.pow(2, 53)},
+      {length: null},
+      {length: -2},
+      {length: new Number(15)}
+    ];
+
+    assert.expect(tricks.length * (functions.length + reducers.length + 4));
+
+    _.each(tricks, function(trick) {
+      var length = trick.length;
+      assert.strictEqual(_.size(trick), 1, 'size on obj with length: ' + length);
+      assert.deepEqual(_.toArray(trick), [length], 'toArray on obj with length: ' + length);
+      assert.deepEqual(_.shuffle(trick), [length], 'shuffle on obj with length: ' + length);
+      assert.deepEqual(_.sample(trick), length, 'sample on obj with length: ' + length);
+
+
+      _.each(functions, function(method) {
+        _[method](trick, function(val, key) {
+          assert.strictEqual(key, 'length', method + ': ran with length = ' + val);
+        });
+      });
+
+      _.each(reducers, function(method) {
+        assert.strictEqual(_[method](trick), trick.length, method);
+      });
+    });
+  });
+
+  QUnit.test('Resistant to collection length and properties changing while iterating', function(assert) {
+
+    var collection = [
+      'each', 'map', 'filter', 'find',
+      'some', 'every', 'max', 'min', 'reject',
+      'groupBy', 'countBy', 'partition', 'indexBy',
+      'reduce', 'reduceRight'
+    ];
+    var array = [
+      'findIndex', 'findLastIndex'
+    ];
+    var object = [
+      'mapObject', 'findKey', 'pick', 'omit'
+    ];
+
+    _.each(collection.concat(array), function(method) {
+      var sparseArray = [1, 2, 3];
+      sparseArray.length = 100;
+      var answers = 0;
+      _[method](sparseArray, function(){
+        ++answers;
+        return method === 'every' ? true : null;
+      }, {});
+      assert.equal(answers, 100, method + ' enumerates [0, length)');
+
+      var growingCollection = [1, 2, 3], count = 0;
+      _[method](growingCollection, function() {
+        if (count < 10) growingCollection.push(count++);
+        return method === 'every' ? true : null;
+      }, {});
+      assert.equal(count, 3, method + ' is resistant to length changes');
+    });
+
+    _.each(collection.concat(object), function(method) {
+      var changingObject = {0: 0, 1: 1}, count = 0;
+      _[method](changingObject, function(val) {
+        if (count < 10) changingObject[++count] = val + 1;
+        return method === 'every' ? true : null;
+      }, {});
+
+      assert.equal(count, 2, method + ' is resistant to property changes');
+    });
+  });
+
+  QUnit.test('map', function(assert) {
+    var doubled = _.map([1, 2, 3], function(num){ return num * 2; });
+    assert.deepEqual(doubled, [2, 4, 6], 'doubled numbers');
+
+    var tripled = _.map([1, 2, 3], function(num){ return num * this.multiplier; }, {multiplier: 3});
+    assert.deepEqual(tripled, [3, 6, 9], 'tripled numbers with context');
+
+    doubled = _([1, 2, 3]).map(function(num){ return num * 2; });
+    assert.deepEqual(doubled, [2, 4, 6], 'OO-style doubled numbers');
+
+    var ids = _.map({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){
+      return n.id;
+    });
+    assert.deepEqual(ids, ['1', '2'], 'Can use collection methods on Array-likes.');
+
+    assert.deepEqual(_.map(null, _.noop), [], 'handles a null properly');
+
+    assert.deepEqual(_.map([1], function() {
+      return this.length;
+    }, [5]), [1], 'called with context');
+
+    // Passing a property name like _.pluck.
+    var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}];
+    assert.deepEqual(_.map(people, 'name'), ['moe', 'curly'], 'predicate string map to object properties');
+  });
+
+  QUnit.test('collect', function(assert) {
+    assert.strictEqual(_.collect, _.map, 'is an alias for map');
+  });
+
+  QUnit.test('reduce', function(assert) {
+    var sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; }, 0);
+    assert.equal(sum, 6, 'can sum up an array');
+
+    var context = {multiplier: 3};
+    sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num * this.multiplier; }, 0, context);
+    assert.equal(sum, 18, 'can reduce with a context object');
+
+    sum = _([1, 2, 3]).reduce(function(memo, num){ return memo + num; }, 0);
+    assert.equal(sum, 6, 'OO-style reduce');
+
+    sum = _.reduce([1, 2, 3], function(memo, num){ return memo + num; });
+    assert.equal(sum, 6, 'default initial value');
+
+    var prod = _.reduce([1, 2, 3, 4], function(memo, num){ return memo * num; });
+    assert.equal(prod, 24, 'can reduce via multiplication');
+
+    assert.ok(_.reduce(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
+    assert.equal(_.reduce([], _.noop, void 0), void 0, 'undefined can be passed as a special case');
+    assert.equal(_.reduce([_], _.noop), _, 'collection of length one with no initial value returns the first item');
+    assert.equal(_.reduce([], _.noop), void 0, 'returns undefined when collection is empty and no initial value');
+  });
+
+  QUnit.test('foldl', function(assert) {
+    assert.strictEqual(_.foldl, _.reduce, 'is an alias for reduce');
+  });
+
+  QUnit.test('inject', function(assert) {
+    assert.strictEqual(_.inject, _.reduce, 'is an alias for reduce');
+  });
+
+  QUnit.test('reduceRight', function(assert) {
+    var list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; }, '');
+    assert.equal(list, 'bazbarfoo', 'can perform right folds');
+
+    list = _.reduceRight(['foo', 'bar', 'baz'], function(memo, str){ return memo + str; });
+    assert.equal(list, 'bazbarfoo', 'default initial value');
+
+    var sum = _.reduceRight({a: 1, b: 2, c: 3}, function(memo, num){ return memo + num; });
+    assert.equal(sum, 6, 'default initial value on object');
+
+    assert.ok(_.reduceRight(null, _.noop, 138) === 138, 'handles a null (with initial value) properly');
+    assert.equal(_.reduceRight([_], _.noop), _, 'collection of length one with no initial value returns the first item');
+
+    assert.equal(_.reduceRight([], _.noop, void 0), void 0, 'undefined can be passed as a special case');
+    assert.equal(_.reduceRight([], _.noop), void 0, 'returns undefined when collection is empty and no initial value');
+
+    // Assert that the correct arguments are being passed.
+
+    var args,
+        init = {},
+        object = {a: 1, b: 2},
+        lastKey = _.keys(object).pop();
+
+    var expected = lastKey === 'a'
+      ? [init, 1, 'a', object]
+      : [init, 2, 'b', object];
+
+    _.reduceRight(object, function() {
+      if (!args) args = _.toArray(arguments);
+    }, init);
+
+    assert.deepEqual(args, expected);
+
+    // And again, with numeric keys.
+
+    object = {2: 'a', 1: 'b'};
+    lastKey = _.keys(object).pop();
+    args = null;
+
+    expected = lastKey === '2'
+      ? [init, 'a', '2', object]
+      : [init, 'b', '1', object];
+
+    _.reduceRight(object, function() {
+      if (!args) args = _.toArray(arguments);
+    }, init);
+
+    assert.deepEqual(args, expected);
+  });
+
+  QUnit.test('foldr', function(assert) {
+    assert.strictEqual(_.foldr, _.reduceRight, 'is an alias for reduceRight');
+  });
+
+  QUnit.test('find', function(assert) {
+    var array = [1, 2, 3, 4];
+    assert.strictEqual(_.find(array, function(n) { return n > 2; }), 3, 'should return first found `value`');
+    assert.strictEqual(_.find(array, function() { return false; }), void 0, 'should return `undefined` if `value` is not found');
+
+    array.dontmatch = 55;
+    assert.strictEqual(_.find(array, function(x) { return x === 55; }), void 0, 'iterates array-likes correctly');
+
+    // Matching an object like _.findWhere.
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}];
+    assert.deepEqual(_.find(list, {a: 1}), {a: 1, b: 2}, 'can be used as findWhere');
+    assert.deepEqual(_.find(list, {b: 4}), {a: 1, b: 4});
+    assert.ok(!_.find(list, {c: 1}), 'undefined when not found');
+    assert.ok(!_.find([], {c: 1}), 'undefined when searching empty list');
+
+    var result = _.find([1, 2, 3], function(num){ return num * 2 === 4; });
+    assert.equal(result, 2, 'found the first "2" and broke the loop');
+
+    var obj = {
+      a: {x: 1, z: 3},
+      b: {x: 2, z: 2},
+      c: {x: 3, z: 4},
+      d: {x: 4, z: 1}
+    };
+
+    assert.deepEqual(_.find(obj, {x: 2}), {x: 2, z: 2}, 'works on objects');
+    assert.deepEqual(_.find(obj, {x: 2, z: 1}), void 0);
+    assert.deepEqual(_.find(obj, function(x) {
+      return x.x === 4;
+    }), {x: 4, z: 1});
+
+    _.findIndex([{a: 1}], function(a, key, o) {
+      assert.equal(key, 0);
+      assert.deepEqual(o, [{a: 1}]);
+      assert.strictEqual(this, _, 'called with context');
+    }, _);
+  });
+
+  QUnit.test('detect', function(assert) {
+    assert.strictEqual(_.detect, _.find, 'is an alias for find');
+  });
+
+  QUnit.test('filter', function(assert) {
+    var evenArray = [1, 2, 3, 4, 5, 6];
+    var evenObject = {one: 1, two: 2, three: 3};
+    var isEven = function(num){ return num % 2 === 0; };
+
+    assert.deepEqual(_.filter(evenArray, isEven), [2, 4, 6]);
+    assert.deepEqual(_.filter(evenObject, isEven), [2], 'can filter objects');
+    assert.deepEqual(_.filter([{}, evenObject, []], 'two'), [evenObject], 'predicate string map to object properties');
+
+    _.filter([1], function() {
+      assert.equal(this, evenObject, 'given context');
+    }, evenObject);
+
+    // Can be used like _.where.
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
+    assert.deepEqual(_.filter(list, {a: 1}), [{a: 1, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}]);
+    assert.deepEqual(_.filter(list, {b: 2}), [{a: 1, b: 2}, {a: 2, b: 2}]);
+    assert.deepEqual(_.filter(list, {}), list, 'Empty object accepts all items');
+    assert.deepEqual(_(list).filter({}), list, 'OO-filter');
+  });
+
+  QUnit.test('select', function(assert) {
+    assert.strictEqual(_.select, _.filter, 'is an alias for filter');
+  });
+
+  QUnit.test('reject', function(assert) {
+    var odds = _.reject([1, 2, 3, 4, 5, 6], function(num){ return num % 2 === 0; });
+    assert.deepEqual(odds, [1, 3, 5], 'rejected each even number');
+
+    var context = 'obj';
+
+    var evens = _.reject([1, 2, 3, 4, 5, 6], function(num){
+      assert.equal(context, 'obj');
+      return num % 2 !== 0;
+    }, context);
+    assert.deepEqual(evens, [2, 4, 6], 'rejected each odd number');
+
+    assert.deepEqual(_.reject([odds, {one: 1, two: 2, three: 3}], 'two'), [odds], 'predicate string map to object properties');
+
+    // Can be used like _.where.
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
+    assert.deepEqual(_.reject(list, {a: 1}), [{a: 2, b: 2}]);
+    assert.deepEqual(_.reject(list, {b: 2}), [{a: 1, b: 3}, {a: 1, b: 4}]);
+    assert.deepEqual(_.reject(list, {}), [], 'Returns empty list given empty object');
+    assert.deepEqual(_.reject(list, []), [], 'Returns empty list given empty array');
+  });
+
+  QUnit.test('every', function(assert) {
+    assert.ok(_.every([], _.identity), 'the empty set');
+    assert.ok(_.every([true, true, true], _.identity), 'every true values');
+    assert.ok(!_.every([true, false, true], _.identity), 'one false value');
+    assert.ok(_.every([0, 10, 28], function(num){ return num % 2 === 0; }), 'even numbers');
+    assert.ok(!_.every([0, 11, 28], function(num){ return num % 2 === 0; }), 'an odd number');
+    assert.ok(_.every([1], _.identity) === true, 'cast to boolean - true');
+    assert.ok(_.every([0], _.identity) === false, 'cast to boolean - false');
+    assert.ok(!_.every([void 0, void 0, void 0], _.identity), 'works with arrays of undefined');
+
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
+    assert.ok(!_.every(list, {a: 1, b: 2}), 'Can be called with object');
+    assert.ok(_.every(list, 'a'), 'String mapped to object property');
+
+    list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}];
+    assert.ok(_.every(list, {b: 2}), 'Can be called with object');
+    assert.ok(!_.every(list, 'c'), 'String mapped to object property');
+
+    assert.ok(_.every({a: 1, b: 2, c: 3, d: 4}, _.isNumber), 'takes objects');
+    assert.ok(!_.every({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects');
+    assert.ok(_.every(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
+    assert.ok(!_.every(['a', 'b', 'c', 'd', 'f'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
+  });
+
+  QUnit.test('all', function(assert) {
+    assert.strictEqual(_.all, _.every, 'is an alias for every');
+  });
+
+  QUnit.test('some', function(assert) {
+    assert.ok(!_.some([]), 'the empty set');
+    assert.ok(!_.some([false, false, false]), 'all false values');
+    assert.ok(_.some([false, false, true]), 'one true value');
+    assert.ok(_.some([null, 0, 'yes', false]), 'a string');
+    assert.ok(!_.some([null, 0, '', false]), 'falsy values');
+    assert.ok(!_.some([1, 11, 29], function(num){ return num % 2 === 0; }), 'all odd numbers');
+    assert.ok(_.some([1, 10, 29], function(num){ return num % 2 === 0; }), 'an even number');
+    assert.ok(_.some([1], _.identity) === true, 'cast to boolean - true');
+    assert.ok(_.some([0], _.identity) === false, 'cast to boolean - false');
+    assert.ok(_.some([false, false, true]));
+
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
+    assert.ok(!_.some(list, {a: 5, b: 2}), 'Can be called with object');
+    assert.ok(_.some(list, 'a'), 'String mapped to object property');
+
+    list = [{a: 1, b: 2}, {a: 2, b: 2, c: true}];
+    assert.ok(_.some(list, {b: 2}), 'Can be called with object');
+    assert.ok(!_.some(list, 'd'), 'String mapped to object property');
+
+    assert.ok(_.some({a: '1', b: '2', c: '3', d: '4', e: 6}, _.isNumber), 'takes objects');
+    assert.ok(!_.some({a: 1, b: 2, c: 3, d: 4}, _.isObject), 'takes objects');
+    assert.ok(_.some(['a', 'b', 'c', 'd'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
+    assert.ok(!_.some(['x', 'y', 'z'], _.hasOwnProperty, {a: 1, b: 2, c: 3, d: 4}), 'context works');
+  });
+
+  QUnit.test('any', function(assert) {
+    assert.strictEqual(_.any, _.some, 'is an alias for some');
+  });
+
+  QUnit.test('includes', function(assert) {
+    _.each([null, void 0, 0, 1, NaN, {}, []], function(val) {
+      assert.strictEqual(_.includes(val, 'hasOwnProperty'), false);
+    });
+    assert.strictEqual(_.includes([1, 2, 3], 2), true, 'two is in the array');
+    assert.ok(!_.includes([1, 3, 9], 2), 'two is not in the array');
+
+    assert.strictEqual(_.includes([5, 4, 3, 2, 1], 5, true), true, 'doesn\'t delegate to binary search');
+
+    assert.ok(_.includes({moe: 1, larry: 3, curly: 9}, 3) === true, '_.includes on objects checks their values');
+    assert.ok(_([1, 2, 3]).includes(2), 'OO-style includes');
+
+    var numbers = [1, 2, 3, 1, 2, 3, 1, 2, 3];
+    assert.strictEqual(_.includes(numbers, 1, 1), true, 'takes a fromIndex');
+    assert.strictEqual(_.includes(numbers, 1, -1), false, 'takes a fromIndex');
+    assert.strictEqual(_.includes(numbers, 1, -2), false, 'takes a fromIndex');
+    assert.strictEqual(_.includes(numbers, 1, -3), true, 'takes a fromIndex');
+    assert.strictEqual(_.includes(numbers, 1, 6), true, 'takes a fromIndex');
+    assert.strictEqual(_.includes(numbers, 1, 7), false, 'takes a fromIndex');
+
+    assert.ok(_.every([1, 2, 3], _.partial(_.includes, numbers)), 'fromIndex is guarded');
+  });
+
+  QUnit.test('include', function(assert) {
+    assert.strictEqual(_.include, _.includes, 'is an alias for includes');
+  });
+
+  QUnit.test('contains', function(assert) {
+    assert.strictEqual(_.contains, _.includes, 'is an alias for includes');
+
+  });
+
+  QUnit.test('includes with NaN', function(assert) {
+    assert.strictEqual(_.includes([1, 2, NaN, NaN], NaN), true, 'Expected [1, 2, NaN] to contain NaN');
+    assert.strictEqual(_.includes([1, 2, Infinity], NaN), false, 'Expected [1, 2, NaN] to contain NaN');
+  });
+
+  QUnit.test('includes with +- 0', function(assert) {
+    _.each([-0, +0], function(val) {
+      assert.strictEqual(_.includes([1, 2, val, val], val), true);
+      assert.strictEqual(_.includes([1, 2, val, val], -val), true);
+      assert.strictEqual(_.includes([-1, 1, 2], -val), false);
+    });
+  });
+
+
+  QUnit.test('invoke', function(assert) {
+    assert.expect(5);
+    var list = [[5, 1, 7], [3, 2, 1]];
+    var result = _.invoke(list, 'sort');
+    assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
+    assert.deepEqual(result[1], [1, 2, 3], 'second array sorted');
+
+    _.invoke([{
+      method: function() {
+        assert.deepEqual(_.toArray(arguments), [1, 2, 3], 'called with arguments');
+      }
+    }], 'method', 1, 2, 3);
+
+    assert.deepEqual(_.invoke([{a: null}, {}, {a: _.constant(1)}], 'a'), [null, void 0, 1], 'handles null & undefined');
+
+    assert.raises(function() {
+      _.invoke([{a: 1}], 'a');
+    }, TypeError, 'throws for non-functions');
+  });
+
+  QUnit.test('invoke w/ function reference', function(assert) {
+    var list = [[5, 1, 7], [3, 2, 1]];
+    var result = _.invoke(list, Array.prototype.sort);
+    assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
+    assert.deepEqual(result[1], [1, 2, 3], 'second array sorted');
+
+    assert.deepEqual(_.invoke([1, 2, 3], function(a) {
+      return a + this;
+    }, 5), [6, 7, 8], 'receives params from invoke');
+  });
+
+  // Relevant when using ClojureScript
+  QUnit.test('invoke when strings have a call method', function(assert) {
+    String.prototype.call = function() {
+      return 42;
+    };
+    var list = [[5, 1, 7], [3, 2, 1]];
+    var s = 'foo';
+    assert.equal(s.call(), 42, 'call function exists');
+    var result = _.invoke(list, 'sort');
+    assert.deepEqual(result[0], [1, 5, 7], 'first array sorted');
+    assert.deepEqual(result[1], [1, 2, 3], 'second array sorted');
+    delete String.prototype.call;
+    assert.equal(s.call, void 0, 'call function removed');
+  });
+
+  QUnit.test('pluck', function(assert) {
+    var people = [{name: 'moe', age: 30}, {name: 'curly', age: 50}];
+    assert.deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'pulls names out of objects');
+    assert.deepEqual(_.pluck(people, 'address'), [void 0, void 0], 'missing properties are returned as undefined');
+    //compat: most flexible handling of edge cases
+    assert.deepEqual(_.pluck([{'[object Object]': 1}], {}), [1]);
+  });
+
+  QUnit.test('where', function(assert) {
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}];
+    var result = _.where(list, {a: 1});
+    assert.equal(result.length, 3);
+    assert.equal(result[result.length - 1].b, 4);
+    result = _.where(list, {b: 2});
+    assert.equal(result.length, 2);
+    assert.equal(result[0].a, 1);
+    result = _.where(list, {});
+    assert.equal(result.length, list.length);
+
+    function test() {}
+    test.map = _.map;
+    assert.deepEqual(_.where([_, {a: 1, b: 2}, _], test), [_, _], 'checks properties given function');
+  });
+
+  QUnit.test('findWhere', function(assert) {
+    var list = [{a: 1, b: 2}, {a: 2, b: 2}, {a: 1, b: 3}, {a: 1, b: 4}, {a: 2, b: 4}];
+    var result = _.findWhere(list, {a: 1});
+    assert.deepEqual(result, {a: 1, b: 2});
+    result = _.findWhere(list, {b: 4});
+    assert.deepEqual(result, {a: 1, b: 4});
+
+    result = _.findWhere(list, {c: 1});
+    assert.ok(_.isUndefined(result), 'undefined when not found');
+
+    result = _.findWhere([], {c: 1});
+    assert.ok(_.isUndefined(result), 'undefined when searching empty list');
+
+    function test() {}
+    test.map = _.map;
+    assert.equal(_.findWhere([_, {a: 1, b: 2}, _], test), _, 'checks properties given function');
+
+    function TestClass() {
+      this.y = 5;
+      this.x = 'foo';
+    }
+    var expect = {c: 1, x: 'foo', y: 5};
+    assert.deepEqual(_.findWhere([{y: 5, b: 6}, expect], new TestClass()), expect, 'uses class instance properties');
+  });
+
+  QUnit.test('max', function(assert) {
+    assert.equal(-Infinity, _.max(null), 'can handle null/undefined');
+    assert.equal(-Infinity, _.max(void 0), 'can handle null/undefined');
+    assert.equal(-Infinity, _.max(null, _.identity), 'can handle null/undefined');
+
+    assert.equal(3, _.max([1, 2, 3]), 'can perform a regular Math.max');
+
+    var neg = _.max([1, 2, 3], function(num){ return -num; });
+    assert.equal(neg, 1, 'can perform a computation-based max');
+
+    assert.equal(-Infinity, _.max({}), 'Maximum value of an empty object');
+    assert.equal(-Infinity, _.max([]), 'Maximum value of an empty array');
+    assert.equal(_.max({a: 'a'}), -Infinity, 'Maximum value of a non-numeric collection');
+
+    assert.equal(299999, _.max(_.range(1, 300000)), 'Maximum value of a too-big array');
+
+    assert.equal(3, _.max([1, 2, 3, 'test']), 'Finds correct max in array starting with num and containing a NaN');
+    assert.equal(3, _.max(['test', 1, 2, 3]), 'Finds correct max in array starting with NaN');
+
+    assert.equal(3, _.max([1, 2, 3, null]), 'Finds correct max in array starting with num and containing a `null`');
+    assert.equal(3, _.max([null, 1, 2, 3]), 'Finds correct max in array starting with a `null`');
+
+    assert.equal(3, _.max([1, 2, 3, '']), 'Finds correct max in array starting with num and containing an empty string');
+    assert.equal(3, _.max(['', 1, 2, 3]), 'Finds correct max in array starting with an empty string');
+
+    assert.equal(3, _.max([1, 2, 3, false]), 'Finds correct max in array starting with num and containing a false');
+    assert.equal(3, _.max([false, 1, 2, 3]), 'Finds correct max in array starting with a false');
+
+    assert.equal(4, _.max([0, 1, 2, 3, 4]), 'Finds correct max in array containing a zero');
+    assert.equal(0, _.max([-3, -2, -1, 0]), 'Finds correct max in array containing negative numbers');
+
+    assert.deepEqual([3, 6], _.map([[1, 2, 3], [4, 5, 6]], _.max), 'Finds correct max in array when mapping through multiple arrays');
+
+    var a = {x: -Infinity};
+    var b = {x: -Infinity};
+    var iterator = function(o){ return o.x; };
+    assert.equal(_.max([a, b], iterator), a, 'Respects iterator return value of -Infinity');
+
+    assert.deepEqual(_.max([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 4}, 'String keys use property iterator');
+
+    assert.deepEqual(_.max([0, 2], function(c){ return c * this.x; }, {x: 1}), 2, 'Iterator context');
+    assert.deepEqual(_.max([[1], [2, 3], [-1, 4], [5]], 0), [5], 'Lookup falsy iterator');
+    assert.deepEqual(_.max([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: 2}, 'Lookup falsy iterator');
+  });
+
+  QUnit.test('min', function(assert) {
+    assert.equal(Infinity, _.min(null), 'can handle null/undefined');
+    assert.equal(Infinity, _.min(void 0), 'can handle null/undefined');
+    assert.equal(Infinity, _.min(null, _.identity), 'can handle null/undefined');
+
+    assert.equal(1, _.min([1, 2, 3]), 'can perform a regular Math.min');
+
+    var neg = _.min([1, 2, 3], function(num){ return -num; });
+    assert.equal(neg, 3, 'can perform a computation-based min');
+
+    assert.equal(Infinity, _.min({}), 'Minimum value of an empty object');
+    assert.equal(Infinity, _.min([]), 'Minimum value of an empty array');
+    assert.equal(_.min({a: 'a'}), Infinity, 'Minimum value of a non-numeric collection');
+
+    assert.deepEqual([1, 4], _.map([[1, 2, 3], [4, 5, 6]], _.min), 'Finds correct min in array when mapping through multiple arrays');
+
+    var now = new Date(9999999999);
+    var then = new Date(0);
+    assert.equal(_.min([now, then]), then);
+
+    assert.equal(1, _.min(_.range(1, 300000)), 'Minimum value of a too-big array');
+
+    assert.equal(1, _.min([1, 2, 3, 'test']), 'Finds correct min in array starting with num and containing a NaN');
+    assert.equal(1, _.min(['test', 1, 2, 3]), 'Finds correct min in array starting with NaN');
+
+    assert.equal(1, _.min([1, 2, 3, null]), 'Finds correct min in array starting with num and containing a `null`');
+    assert.equal(1, _.min([null, 1, 2, 3]), 'Finds correct min in array starting with a `null`');
+
+    assert.equal(0, _.min([0, 1, 2, 3, 4]), 'Finds correct min in array containing a zero');
+    assert.equal(-3, _.min([-3, -2, -1, 0]), 'Finds correct min in array containing negative numbers');
+
+    var a = {x: Infinity};
+    var b = {x: Infinity};
+    var iterator = function(o){ return o.x; };
+    assert.equal(_.min([a, b], iterator), a, 'Respects iterator return value of Infinity');
+
+    assert.deepEqual(_.min([{a: 1}, {a: 0, b: 3}, {a: 4}, {a: 2}], 'a'), {a: 0, b: 3}, 'String keys use property iterator');
+
+    assert.deepEqual(_.min([0, 2], function(c){ return c * this.x; }, {x: -1}), 2, 'Iterator context');
+    assert.deepEqual(_.min([[1], [2, 3], [-1, 4], [5]], 0), [-1, 4], 'Lookup falsy iterator');
+    assert.deepEqual(_.min([{0: 1}, {0: 2}, {0: -1}, {a: 1}], 0), {0: -1}, 'Lookup falsy iterator');
+  });
+
+  QUnit.test('sortBy', function(assert) {
+    var people = [{name: 'curly', age: 50}, {name: 'moe', age: 30}];
+    people = _.sortBy(people, function(person){ return person.age; });
+    assert.deepEqual(_.pluck(people, 'name'), ['moe', 'curly'], 'stooges sorted by age');
+
+    var list = [void 0, 4, 1, void 0, 3, 2];
+    assert.deepEqual(_.sortBy(list, _.identity), [1, 2, 3, 4, void 0, void 0], 'sortBy with undefined values');
+
+    list = ['one', 'two', 'three', 'four', 'five'];
+    var sorted = _.sortBy(list, 'length');
+    assert.deepEqual(sorted, ['one', 'two', 'four', 'five', 'three'], 'sorted by length');
+
+    function Pair(x, y) {
+      this.x = x;
+      this.y = y;
+    }
+
+    var stableArray = [
+      new Pair(1, 1), new Pair(1, 2),
+      new Pair(1, 3), new Pair(1, 4),
+      new Pair(1, 5), new Pair(1, 6),
+      new Pair(2, 1), new Pair(2, 2),
+      new Pair(2, 3), new Pair(2, 4),
+      new Pair(2, 5), new Pair(2, 6),
+      new Pair(void 0, 1), new Pair(void 0, 2),
+      new Pair(void 0, 3), new Pair(void 0, 4),
+      new Pair(void 0, 5), new Pair(void 0, 6)
+    ];
+
+    var stableObject = _.object('abcdefghijklmnopqr'.split(''), stableArray);
+
+    var actual = _.sortBy(stableArray, function(pair) {
+      return pair.x;
+    });
+
+    assert.deepEqual(actual, stableArray, 'sortBy should be stable for arrays');
+    assert.deepEqual(_.sortBy(stableArray, 'x'), stableArray, 'sortBy accepts property string');
+
+    actual = _.sortBy(stableObject, function(pair) {
+      return pair.x;
+    });
+
+    assert.deepEqual(actual, stableArray, 'sortBy should be stable for objects');
+
+    list = ['q', 'w', 'e', 'r', 't', 'y'];
+    assert.deepEqual(_.sortBy(list), ['e', 'q', 'r', 't', 'w', 'y'], 'uses _.identity if iterator is not specified');
+  });
+
+  QUnit.test('groupBy', function(assert) {
+    var parity = _.groupBy([1, 2, 3, 4, 5, 6], function(num){ return num % 2; });
+    assert.ok('0' in parity && '1' in parity, 'created a group for each value');
+    assert.deepEqual(parity[0], [2, 4, 6], 'put each even number in the right group');
+
+    var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
+    var grouped = _.groupBy(list, 'length');
+    assert.deepEqual(grouped['3'], ['one', 'two', 'six', 'ten']);
+    assert.deepEqual(grouped['4'], ['four', 'five', 'nine']);
+    assert.deepEqual(grouped['5'], ['three', 'seven', 'eight']);
+
+    var context = {};
+    _.groupBy([{}], function(){ assert.ok(this === context); }, context);
+
+    grouped = _.groupBy([4.2, 6.1, 6.4], function(num) {
+      return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
+    });
+    assert.equal(grouped.constructor.length, 1);
+    assert.equal(grouped.hasOwnProperty.length, 2);
+
+    var array = [{}];
+    _.groupBy(array, function(value, index, obj){ assert.ok(obj === array); });
+
+    array = [1, 2, 1, 2, 3];
+    grouped = _.groupBy(array);
+    assert.equal(grouped['1'].length, 2);
+    assert.equal(grouped['3'].length, 1);
+
+    var matrix = [
+      [1, 2],
+      [1, 3],
+      [2, 3]
+    ];
+    assert.deepEqual(_.groupBy(matrix, 0), {1: [[1, 2], [1, 3]], 2: [[2, 3]]});
+    assert.deepEqual(_.groupBy(matrix, 1), {2: [[1, 2]], 3: [[1, 3], [2, 3]]});
+  });
+
+  QUnit.test('indexBy', function(assert) {
+    var parity = _.indexBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
+    assert.equal(parity['true'], 4);
+    assert.equal(parity['false'], 5);
+
+    var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
+    var grouped = _.indexBy(list, 'length');
+    assert.equal(grouped['3'], 'ten');
+    assert.equal(grouped['4'], 'nine');
+    assert.equal(grouped['5'], 'eight');
+
+    var array = [1, 2, 1, 2, 3];
+    grouped = _.indexBy(array);
+    assert.equal(grouped['1'], 1);
+    assert.equal(grouped['2'], 2);
+    assert.equal(grouped['3'], 3);
+  });
+
+  QUnit.test('countBy', function(assert) {
+    var parity = _.countBy([1, 2, 3, 4, 5], function(num){ return num % 2 === 0; });
+    assert.equal(parity['true'], 2);
+    assert.equal(parity['false'], 3);
+
+    var list = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
+    var grouped = _.countBy(list, 'length');
+    assert.equal(grouped['3'], 4);
+    assert.equal(grouped['4'], 3);
+    assert.equal(grouped['5'], 3);
+
+    var context = {};
+    _.countBy([{}], function(){ assert.ok(this === context); }, context);
+
+    grouped = _.countBy([4.2, 6.1, 6.4], function(num) {
+      return Math.floor(num) > 4 ? 'hasOwnProperty' : 'constructor';
+    });
+    assert.equal(grouped.constructor, 1);
+    assert.equal(grouped.hasOwnProperty, 2);
+
+    var array = [{}];
+    _.countBy(array, function(value, index, obj){ assert.ok(obj === array); });
+
+    array = [1, 2, 1, 2, 3];
+    grouped = _.countBy(array);
+    assert.equal(grouped['1'], 2);
+    assert.equal(grouped['3'], 1);
+  });
+
+  QUnit.test('shuffle', function(assert) {
+    assert.deepEqual(_.shuffle([1]), [1], 'behaves correctly on size 1 arrays');
+    var numbers = _.range(20);
+    var shuffled = _.shuffle(numbers);
+    assert.notDeepEqual(numbers, shuffled, 'does change the order'); // Chance of false negative: 1 in ~2.4*10^18
+    assert.notStrictEqual(numbers, shuffled, 'original object is unmodified');
+    assert.deepEqual(numbers, _.sortBy(shuffled), 'contains the same members before and after shuffle');
+
+    shuffled = _.shuffle({a: 1, b: 2, c: 3, d: 4});
+    assert.equal(shuffled.length, 4);
+    assert.deepEqual(shuffled.sort(), [1, 2, 3, 4], 'works on objects');
+  });
+
+  QUnit.test('sample', function(assert) {
+    assert.strictEqual(_.sample([1]), 1, 'behaves correctly when no second parameter is given');
+    assert.deepEqual(_.sample([1, 2, 3], -2), [], 'behaves correctly on negative n');
+    var numbers = _.range(10);
+    var allSampled = _.sample(numbers, 10).sort();
+    assert.deepEqual(allSampled, numbers, 'contains the same members before and after sample');
+    allSampled = _.sample(numbers, 20).sort();
+    assert.deepEqual(allSampled, numbers, 'also works when sampling more objects than are present');
+    assert.ok(_.contains(numbers, _.sample(numbers)), 'sampling a single element returns something from the array');
+    assert.strictEqual(_.sample([]), void 0, 'sampling empty array with no number returns undefined');
+    assert.notStrictEqual(_.sample([], 5), [], 'sampling empty array with a number returns an empty array');
+    assert.notStrictEqual(_.sample([1, 2, 3], 0), [], 'sampling an array with 0 picks returns an empty array');
+    assert.deepEqual(_.sample([1, 2], -1), [], 'sampling a negative number of picks returns an empty array');
+    assert.ok(_.contains([1, 2, 3], _.sample({a: 1, b: 2, c: 3})), 'sample one value from an object');
+    var partialSample = _.sample(_.range(1000), 10);
+    var partialSampleSorted = partialSample.sort();
+    assert.notDeepEqual(partialSampleSorted, _.range(10), 'samples from the whole array, not just the beginning');
+  });
+
+  QUnit.test('toArray', function(assert) {
+    assert.ok(!_.isArray(arguments), 'arguments object is not an array');
+    assert.ok(_.isArray(_.toArray(arguments)), 'arguments object converted into array');
+    var a = [1, 2, 3];
+    assert.ok(_.toArray(a) !== a, 'array is cloned');
+    assert.deepEqual(_.toArray(a), [1, 2, 3], 'cloned array contains same elements');
+
+    var numbers = _.toArray({one: 1, two: 2, three: 3});
+    assert.deepEqual(numbers, [1, 2, 3], 'object flattened into array');
+
+    var hearts = '\uD83D\uDC95';
+    var pair = hearts.split('');
+    var expected = [pair[0], hearts, '&', hearts, pair[1]];
+    assert.deepEqual(_.toArray(expected.join('')), expected, 'maintains astral characters');
+    assert.deepEqual(_.toArray(''), [], 'empty string into empty array');
+
+    if (typeof document != 'undefined') {
+      // test in IE < 9
+      var actual;
+      try {
+        actual = _.toArray(document.childNodes);
+      } catch (e) { /* ignored */ }
+      assert.deepEqual(actual, _.map(document.childNodes, _.identity), 'works on NodeList');
+    }
+  });
+
+  QUnit.test('size', function(assert) {
+    assert.equal(_.size({one: 1, two: 2, three: 3}), 3, 'can compute the size of an object');
+    assert.equal(_.size([1, 2, 3]), 3, 'can compute the size of an array');
+    assert.equal(_.size({length: 3, 0: 0, 1: 0, 2: 0}), 3, 'can compute the size of Array-likes');
+
+    var func = function() {
+      return _.size(arguments);
+    };
+
+    assert.equal(func(1, 2, 3, 4), 4, 'can test the size of the arguments object');
+
+    assert.equal(_.size('hello'), 5, 'can compute the size of a string literal');
+    assert.equal(_.size(new String('hello')), 5, 'can compute the size of string object');
+
+    assert.equal(_.size(null), 0, 'handles nulls');
+    assert.equal(_.size(0), 0, 'handles numbers');
+  });
+
+  QUnit.test('partition', function(assert) {
+    var list = [0, 1, 2, 3, 4, 5];
+    assert.deepEqual(_.partition(list, function(x) { return x < 4; }), [[0, 1, 2, 3], [4, 5]], 'handles bool return values');
+    assert.deepEqual(_.partition(list, function(x) { return x & 1; }), [[1, 3, 5], [0, 2, 4]], 'handles 0 and 1 return values');
+    assert.deepEqual(_.partition(list, function(x) { return x - 3; }), [[0, 1, 2, 4, 5], [3]], 'handles other numeric return values');
+    assert.deepEqual(_.partition(list, function(x) { return x > 1 ? null : true; }), [[0, 1], [2, 3, 4, 5]], 'handles null return values');
+    assert.deepEqual(_.partition(list, function(x) { if (x < 2) return true; }), [[0, 1], [2, 3, 4, 5]], 'handles undefined return values');
+    assert.deepEqual(_.partition({a: 1, b: 2, c: 3}, function(x) { return x > 1; }), [[2, 3], [1]], 'handles objects');
+
+    assert.deepEqual(_.partition(list, function(x, index) { return index % 2; }), [[1, 3, 5], [0, 2, 4]], 'can reference the array index');
+    assert.deepEqual(_.partition(list, function(x, index, arr) { return x === arr.length - 1; }), [[5], [0, 1, 2, 3, 4]], 'can reference the collection');
+
+    // Default iterator
+    assert.deepEqual(_.partition([1, false, true, '']), [[1, true], [false, '']], 'Default iterator');
+    assert.deepEqual(_.partition([{x: 1}, {x: 0}, {x: 1}], 'x'), [[{x: 1}, {x: 1}], [{x: 0}]], 'Takes a string');
+
+    // Context
+    var predicate = function(x){ return x === this.x; };
+    assert.deepEqual(_.partition([1, 2, 3], predicate, {x: 2}), [[2], [1, 3]], 'partition takes a context argument');
+
+    assert.deepEqual(_.partition([{a: 1}, {b: 2}, {a: 1, b: 2}], {a: 1}), [[{a: 1}, {a: 1, b: 2}], [{b: 2}]], 'predicate can be object');
+
+    var object = {a: 1};
+    _.partition(object, function(val, key, obj) {
+      assert.equal(val, 1);
+      assert.equal(key, 'a');
+      assert.equal(obj, object);
+      assert.equal(this, predicate);
+    }, predicate);
+  });
+
+  if (typeof document != 'undefined') {
+    QUnit.test('Can use various collection methods on NodeLists', function(assert) {
+      var parent = document.createElement('div');
+      parent.innerHTML = '<span id=id1></span>textnode<span id=id2></span>';
+
+      var elementChildren = _.filter(parent.childNodes, _.isElement);
+      assert.equal(elementChildren.length, 2);
+
+      assert.deepEqual(_.map(elementChildren, 'id'), ['id1', 'id2']);
+      assert.deepEqual(_.map(parent.childNodes, 'nodeType'), [1, 3, 1]);
+
+      assert.ok(!_.every(parent.childNodes, _.isElement));
+      assert.ok(_.some(parent.childNodes, _.isElement));
+
+      function compareNode(node) {
+        return _.isElement(node) ? node.id.charAt(2) : void 0;
+      }
+      assert.equal(_.max(parent.childNodes, compareNode), _.last(parent.childNodes));
+      assert.equal(_.min(parent.childNodes, compareNode), _.first(parent.childNodes));
+    });
+  }
+
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/cross-document.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/cross-document.js
new file mode 100644
index 0000000..cb68a3d
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/cross-document.js
@@ -0,0 +1,141 @@
+(function() {
+  if (typeof document == 'undefined') return;
+
+  var _ = typeof require == 'function' ? require('..') : window._;
+
+  QUnit.module('Cross Document');
+  /* global iObject, iElement, iArguments, iFunction, iArray, iError, iString, iNumber, iBoolean, iDate, iRegExp, iNaN, iNull, iUndefined, ActiveXObject */
+
+  // Setup remote variables for iFrame tests.
+  var iframe = document.createElement('iframe');
+  iframe.frameBorder = iframe.height = iframe.width = 0;
+  document.body.appendChild(iframe);
+  var iDoc = (iDoc = iframe.contentDocument || iframe.contentWindow).document || iDoc;
+  iDoc.write(
+    [
+      '<script>',
+      'parent.iElement = document.createElement("div");',
+      'parent.iArguments = (function(){ return arguments; })(1, 2, 3);',
+      'parent.iArray = [1, 2, 3];',
+      'parent.iString = new String("hello");',
+      'parent.iNumber = new Number(100);',
+      'parent.iFunction = (function(){});',
+      'parent.iDate = new Date();',
+      'parent.iRegExp = /hi/;',
+      'parent.iNaN = NaN;',
+      'parent.iNull = null;',
+      'parent.iBoolean = new Boolean(false);',
+      'parent.iUndefined = undefined;',
+      'parent.iObject = {};',
+      'parent.iError = new Error();',
+      '</script>'
+    ].join('\n')
+  );
+  iDoc.close();
+
+  QUnit.test('isEqual', function(assert) {
+
+    assert.ok(!_.isEqual(iNumber, 101));
+    assert.ok(_.isEqual(iNumber, 100));
+
+    // Objects from another frame.
+    assert.ok(_.isEqual({}, iObject), 'Objects with equivalent members created in different documents are equal');
+
+    // Array from another frame.
+    assert.ok(_.isEqual([1, 2, 3], iArray), 'Arrays with equivalent elements created in different documents are equal');
+  });
+
+  QUnit.test('isEmpty', function(assert) {
+    assert.ok(!_([iNumber]).isEmpty(), '[1] is not empty');
+    assert.ok(!_.isEmpty(iArray), '[] is empty');
+    assert.ok(_.isEmpty(iObject), '{} is empty');
+  });
+
+  QUnit.test('isElement', function(assert) {
+    assert.ok(!_.isElement('div'), 'strings are not dom elements');
+    assert.ok(_.isElement(document.body), 'the body tag is a DOM element');
+    assert.ok(_.isElement(iElement), 'even from another frame');
+  });
+
+  QUnit.test('isArguments', function(assert) {
+    assert.ok(_.isArguments(iArguments), 'even from another frame');
+  });
+
+  QUnit.test('isObject', function(assert) {
+    assert.ok(_.isObject(iElement), 'even from another frame');
+    assert.ok(_.isObject(iFunction), 'even from another frame');
+  });
+
+  QUnit.test('isArray', function(assert) {
+    assert.ok(_.isArray(iArray), 'even from another frame');
+  });
+
+  QUnit.test('isString', function(assert) {
+    assert.ok(_.isString(iString), 'even from another frame');
+  });
+
+  QUnit.test('isNumber', function(assert) {
+    assert.ok(_.isNumber(iNumber), 'even from another frame');
+  });
+
+  QUnit.test('isBoolean', function(assert) {
+    assert.ok(_.isBoolean(iBoolean), 'even from another frame');
+  });
+
+  QUnit.test('isFunction', function(assert) {
+    assert.ok(_.isFunction(iFunction), 'even from another frame');
+  });
+
+  QUnit.test('isDate', function(assert) {
+    assert.ok(_.isDate(iDate), 'even from another frame');
+  });
+
+  QUnit.test('isRegExp', function(assert) {
+    assert.ok(_.isRegExp(iRegExp), 'even from another frame');
+  });
+
+  QUnit.test('isNaN', function(assert) {
+    assert.ok(_.isNaN(iNaN), 'even from another frame');
+  });
+
+  QUnit.test('isNull', function(assert) {
+    assert.ok(_.isNull(iNull), 'even from another frame');
+  });
+
+  QUnit.test('isUndefined', function(assert) {
+    assert.ok(_.isUndefined(iUndefined), 'even from another frame');
+  });
+
+  QUnit.test('isError', function(assert) {
+    assert.ok(_.isError(iError), 'even from another frame');
+  });
+
+  if (typeof ActiveXObject != 'undefined') {
+    QUnit.test('IE host objects', function(assert) {
+      var xml = new ActiveXObject('Msxml2.DOMDocument.3.0');
+      assert.ok(!_.isNumber(xml));
+      assert.ok(!_.isBoolean(xml));
+      assert.ok(!_.isNaN(xml));
+      assert.ok(!_.isFunction(xml));
+      assert.ok(!_.isNull(xml));
+      assert.ok(!_.isUndefined(xml));
+    });
+
+    QUnit.test('#1621 IE 11 compat mode DOM elements are not functions', function(assert) {
+      var fn = function() {};
+      var xml = new ActiveXObject('Msxml2.DOMDocument.3.0');
+      var div = document.createElement('div');
+
+      // JIT the function
+      var count = 200;
+      while (count--) {
+        _.isFunction(fn);
+      }
+
+      assert.equal(_.isFunction(xml), false);
+      assert.equal(_.isFunction(div), false);
+      assert.equal(_.isFunction(fn), true);
+    });
+  }
+
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/functions.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/functions.js
new file mode 100644
index 0000000..f696bd6
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/functions.js
@@ -0,0 +1,728 @@
+(function() {
+  var _ = typeof require == 'function' ? require('..') : window._;
+
+  QUnit.module('Functions');
+  QUnit.config.asyncRetries = 3;
+
+  QUnit.test('bind', function(assert) {
+    var context = {name: 'moe'};
+    var func = function(arg) { return 'name: ' + (this.name || arg); };
+    var bound = _.bind(func, context);
+    assert.equal(bound(), 'name: moe', 'can bind a function to a context');
+
+    bound = _(func).bind(context);
+    assert.equal(bound(), 'name: moe', 'can do OO-style binding');
+
+    bound = _.bind(func, null, 'curly');
+    var result = bound();
+    // Work around a PhantomJS bug when applying a function with null|undefined.
+    assert.ok(result === 'name: curly' || result === 'name: ' + window.name, 'can bind without specifying a context');
+
+    func = function(salutation, name) { return salutation + ': ' + name; };
+    func = _.bind(func, this, 'hello');
+    assert.equal(func('moe'), 'hello: moe', 'the function was partially applied in advance');
+
+    func = _.bind(func, this, 'curly');
+    assert.equal(func(), 'hello: curly', 'the function was completely applied in advance');
+
+    func = function(salutation, firstname, lastname) { return salutation + ': ' + firstname + ' ' + lastname; };
+    func = _.bind(func, this, 'hello', 'moe', 'curly');
+    assert.equal(func(), 'hello: moe curly', 'the function was partially applied in advance and can accept multiple arguments');
+
+    func = function(ctx, message) { assert.equal(this, ctx, message); };
+    _.bind(func, 0, 0, 'can bind a function to `0`')();
+    _.bind(func, '', '', 'can bind a function to an empty string')();
+    _.bind(func, false, false, 'can bind a function to `false`')();
+
+    // These tests are only meaningful when using a browser without a native bind function
+    // To test this with a modern browser, set underscore's nativeBind to undefined
+    var F = function() { return this; };
+    var boundf = _.bind(F, {hello: 'moe curly'});
+    var Boundf = boundf; // make eslint happy.
+    var newBoundf = new Boundf();
+    assert.equal(newBoundf.hello, void 0, 'function should not be bound to the context, to comply with ECMAScript 5');
+    assert.equal(boundf().hello, 'moe curly', "When called without the new operator, it's OK to be bound to the context");
+    assert.ok(newBoundf instanceof F, 'a bound instance is an instance of the original function');
+
+    assert.raises(function() { _.bind('notafunction'); }, TypeError, 'throws an error when binding to a non-function');
+  });
+
+  QUnit.test('partial', function(assert) {
+    var obj = {name: 'moe'};
+    var func = function() { return this.name + ' ' + _.toArray(arguments).join(' '); };
+
+    obj.func = _.partial(func, 'a', 'b');
+    assert.equal(obj.func('c', 'd'), 'moe a b c d', 'can partially apply');
+
+    obj.func = _.partial(func, _, 'b', _, 'd');
+    assert.equal(obj.func('a', 'c'), 'moe a b c d', 'can partially apply with placeholders');
+
+    func = _.partial(function() { return arguments.length; }, _, 'b', _, 'd');
+    assert.equal(func('a', 'c', 'e'), 5, 'accepts more arguments than the number of placeholders');
+    assert.equal(func('a'), 4, 'accepts fewer arguments than the number of placeholders');
+
+    func = _.partial(function() { return typeof arguments[2]; }, _, 'b', _, 'd');
+    assert.equal(func('a'), 'undefined', 'unfilled placeholders are undefined');
+
+    // passes context
+    function MyWidget(name, options) {
+      this.name = name;
+      this.options = options;
+    }
+    MyWidget.prototype.get = function() {
+      return this.name;
+    };
+    var MyWidgetWithCoolOpts = _.partial(MyWidget, _, {a: 1});
+    var widget = new MyWidgetWithCoolOpts('foo');
+    assert.ok(widget instanceof MyWidget, 'Can partially bind a constructor');
+    assert.equal(widget.get(), 'foo', 'keeps prototype');
+    assert.deepEqual(widget.options, {a: 1});
+
+    _.partial.placeholder = obj;
+    func = _.partial(function() { return arguments.length; }, obj, 'b', obj, 'd');
+    assert.equal(func('a'), 4, 'allows the placeholder to be swapped out');
+
+    _.partial.placeholder = {};
+    func = _.partial(function() { return arguments.length; }, obj, 'b', obj, 'd');
+    assert.equal(func('a'), 5, 'swapping the placeholder preserves previously bound arguments');
+
+    _.partial.placeholder = _;
+  });
+
+  QUnit.test('bindAll', function(assert) {
+    var curly = {name: 'curly'};
+    var moe = {
+      name: 'moe',
+      getName: function() { return 'name: ' + this.name; },
+      sayHi: function() { return 'hi: ' + this.name; }
+    };
+    curly.getName = moe.getName;
+    _.bindAll(moe, 'getName', 'sayHi');
+    curly.sayHi = moe.sayHi;
+    assert.equal(curly.getName(), 'name: curly', 'unbound function is bound to current object');
+    assert.equal(curly.sayHi(), 'hi: moe', 'bound function is still bound to original object');
+
+    curly = {name: 'curly'};
+    moe = {
+      name: 'moe',
+      getName: function() { return 'name: ' + this.name; },
+      sayHi: function() { return 'hi: ' + this.name; },
+      sayLast: function() { return this.sayHi(_.last(arguments)); }
+    };
+
+    assert.raises(function() { _.bindAll(moe); }, Error, 'throws an error for bindAll with no functions named');
+    assert.raises(function() { _.bindAll(moe, 'sayBye'); }, TypeError, 'throws an error for bindAll if the given key is undefined');
+    assert.raises(function() { _.bindAll(moe, 'name'); }, TypeError, 'throws an error for bindAll if the given key is not a function');
+
+    _.bindAll(moe, 'sayHi', 'sayLast');
+    curly.sayHi = moe.sayHi;
+    assert.equal(curly.sayHi(), 'hi: moe');
+
+    var sayLast = moe.sayLast;
+    assert.equal(sayLast(1, 2, 3, 4, 5, 6, 7, 'Tom'), 'hi: moe', 'createCallback works with any number of arguments');
+
+    _.bindAll(moe, ['getName']);
+    var getName = moe.getName;
+    assert.equal(getName(), 'name: moe', 'flattens arguments into a single list');
+  });
+
+  QUnit.test('memoize', function(assert) {
+    var fib = function(n) {
+      return n < 2 ? n : fib(n - 1) + fib(n - 2);
+    };
+    assert.equal(fib(10), 55, 'a memoized version of fibonacci produces identical results');
+    fib = _.memoize(fib); // Redefine `fib` for memoization
+    assert.equal(fib(10), 55, 'a memoized version of fibonacci produces identical results');
+
+    var o = function(str) {
+      return str;
+    };
+    var fastO = _.memoize(o);
+    assert.equal(o('toString'), 'toString', 'checks hasOwnProperty');
+    assert.equal(fastO('toString'), 'toString', 'checks hasOwnProperty');
+
+    // Expose the cache.
+    var upper = _.memoize(function(s) {
+      return s.toUpperCase();
+    });
+    assert.equal(upper('foo'), 'FOO');
+    assert.equal(upper('bar'), 'BAR');
+    assert.deepEqual(upper.cache, {foo: 'FOO', bar: 'BAR'});
+    upper.cache = {foo: 'BAR', bar: 'FOO'};
+    assert.equal(upper('foo'), 'BAR');
+    assert.equal(upper('bar'), 'FOO');
+
+    var hashed = _.memoize(function(key) {
+      //https://github.com/jashkenas/underscore/pull/1679#discussion_r13736209
+      assert.ok(/[a-z]+/.test(key), 'hasher doesn\'t change keys');
+      return key;
+    }, function(key) {
+      return key.toUpperCase();
+    });
+    hashed('yep');
+    assert.deepEqual(hashed.cache, {YEP: 'yep'}, 'takes a hasher');
+
+    // Test that the hash function can be used to swizzle the key.
+    var objCacher = _.memoize(function(value, key) {
+      return {key: key, value: value};
+    }, function(value, key) {
+      return key;
+    });
+    var myObj = objCacher('a', 'alpha');
+    var myObjAlias = objCacher('b', 'alpha');
+    assert.notStrictEqual(myObj, void 0, 'object is created if second argument used as key');
+    assert.strictEqual(myObj, myObjAlias, 'object is cached if second argument used as key');
+    assert.strictEqual(myObj.value, 'a', 'object is not modified if second argument used as key');
+  });
+
+  QUnit.test('delay', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var delayed = false;
+    _.delay(function(){ delayed = true; }, 100);
+    setTimeout(function(){ assert.ok(!delayed, "didn't delay the function quite yet"); }, 50);
+    setTimeout(function(){ assert.ok(delayed, 'delayed the function'); done(); }, 150);
+  });
+
+  QUnit.test('defer', function(assert) {
+    assert.expect(1);
+    var done = assert.async();
+    var deferred = false;
+    _.defer(function(bool){ deferred = bool; }, true);
+    _.delay(function(){ assert.ok(deferred, 'deferred the function'); done(); }, 50);
+  });
+
+  QUnit.test('throttle', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 32);
+    throttledIncr(); throttledIncr();
+
+    assert.equal(counter, 1, 'incr was called immediately');
+    _.delay(function(){ assert.equal(counter, 2, 'incr was throttled'); done(); }, 64);
+  });
+
+  QUnit.test('throttle arguments', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var value = 0;
+    var update = function(val){ value = val; };
+    var throttledUpdate = _.throttle(update, 32);
+    throttledUpdate(1); throttledUpdate(2);
+    _.delay(function(){ throttledUpdate(3); }, 64);
+    assert.equal(value, 1, 'updated to latest value');
+    _.delay(function(){ assert.equal(value, 3, 'updated to latest value'); done(); }, 96);
+  });
+
+  QUnit.test('throttle once', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ return ++counter; };
+    var throttledIncr = _.throttle(incr, 32);
+    var result = throttledIncr();
+    _.delay(function(){
+      assert.equal(result, 1, 'throttled functions return their value');
+      assert.equal(counter, 1, 'incr was called once'); done();
+    }, 64);
+  });
+
+  QUnit.test('throttle twice', function(assert) {
+    assert.expect(1);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 32);
+    throttledIncr(); throttledIncr();
+    _.delay(function(){ assert.equal(counter, 2, 'incr was called twice'); done(); }, 64);
+  });
+
+  QUnit.test('more throttling', function(assert) {
+    assert.expect(3);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 30);
+    throttledIncr(); throttledIncr();
+    assert.equal(counter, 1);
+    _.delay(function(){
+      assert.equal(counter, 2);
+      throttledIncr();
+      assert.equal(counter, 3);
+      done();
+    }, 85);
+  });
+
+  QUnit.test('throttle repeatedly with results', function(assert) {
+    assert.expect(6);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ return ++counter; };
+    var throttledIncr = _.throttle(incr, 100);
+    var results = [];
+    var saveResult = function() { results.push(throttledIncr()); };
+    saveResult(); saveResult();
+    _.delay(saveResult, 50);
+    _.delay(saveResult, 150);
+    _.delay(saveResult, 160);
+    _.delay(saveResult, 230);
+    _.delay(function() {
+      assert.equal(results[0], 1, 'incr was called once');
+      assert.equal(results[1], 1, 'incr was throttled');
+      assert.equal(results[2], 1, 'incr was throttled');
+      assert.equal(results[3], 2, 'incr was called twice');
+      assert.equal(results[4], 2, 'incr was throttled');
+      assert.equal(results[5], 3, 'incr was called trailing');
+      done();
+    }, 300);
+  });
+
+  QUnit.test('throttle triggers trailing call when invoked repeatedly', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var limit = 48;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 32);
+
+    var stamp = new Date;
+    while (new Date - stamp < limit) {
+      throttledIncr();
+    }
+    var lastCount = counter;
+    assert.ok(counter > 1);
+
+    _.delay(function() {
+      assert.ok(counter > lastCount);
+      done();
+    }, 96);
+  });
+
+  QUnit.test('throttle does not trigger leading call when leading is set to false', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 60, {leading: false});
+
+    throttledIncr(); throttledIncr();
+    assert.equal(counter, 0);
+
+    _.delay(function() {
+      assert.equal(counter, 1);
+      done();
+    }, 96);
+  });
+
+  QUnit.test('more throttle does not trigger leading call when leading is set to false', function(assert) {
+    assert.expect(3);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 100, {leading: false});
+
+    throttledIncr();
+    _.delay(throttledIncr, 50);
+    _.delay(throttledIncr, 60);
+    _.delay(throttledIncr, 200);
+    assert.equal(counter, 0);
+
+    _.delay(function() {
+      assert.equal(counter, 1);
+    }, 250);
+
+    _.delay(function() {
+      assert.equal(counter, 2);
+      done();
+    }, 350);
+  });
+
+  QUnit.test('one more throttle with leading: false test', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 100, {leading: false});
+
+    var time = new Date;
+    while (new Date - time < 350) throttledIncr();
+    assert.ok(counter <= 3);
+
+    _.delay(function() {
+      assert.ok(counter <= 4);
+      done();
+    }, 200);
+  });
+
+  QUnit.test('throttle does not trigger trailing call when trailing is set to false', function(assert) {
+    assert.expect(4);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 60, {trailing: false});
+
+    throttledIncr(); throttledIncr(); throttledIncr();
+    assert.equal(counter, 1);
+
+    _.delay(function() {
+      assert.equal(counter, 1);
+
+      throttledIncr(); throttledIncr();
+      assert.equal(counter, 2);
+
+      _.delay(function() {
+        assert.equal(counter, 2);
+        done();
+      }, 96);
+    }, 96);
+  });
+
+  QUnit.test('throttle continues to function after system time is set backwards', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 100);
+    var origNowFunc = _.now;
+
+    throttledIncr();
+    assert.equal(counter, 1);
+    _.now = function() {
+      return new Date(2013, 0, 1, 1, 1, 1);
+    };
+
+    _.delay(function() {
+      throttledIncr();
+      assert.equal(counter, 2);
+      done();
+      _.now = origNowFunc;
+    }, 200);
+  });
+
+  QUnit.test('throttle re-entrant', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var sequence = [
+      ['b1', 'b2'],
+      ['c1', 'c2']
+    ];
+    var value = '';
+    var throttledAppend;
+    var append = function(arg){
+      value += this + arg;
+      var args = sequence.pop();
+      if (args) {
+        throttledAppend.call(args[0], args[1]);
+      }
+    };
+    throttledAppend = _.throttle(append, 32);
+    throttledAppend.call('a1', 'a2');
+    assert.equal(value, 'a1a2');
+    _.delay(function(){
+      assert.equal(value, 'a1a2c1c2b1b2', 'append was throttled successfully');
+      done();
+    }, 100);
+  });
+
+  QUnit.test('throttle cancel', function(assert) {
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 32);
+    throttledIncr();
+    throttledIncr.cancel();
+    throttledIncr();
+    throttledIncr();
+
+    assert.equal(counter, 2, 'incr was called immediately');
+    _.delay(function(){ assert.equal(counter, 3, 'incr was throttled'); done(); }, 64);
+  });
+
+  QUnit.test('throttle cancel with leading: false', function(assert) {
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var throttledIncr = _.throttle(incr, 32, {leading: false});
+    throttledIncr();
+    throttledIncr.cancel();
+
+    assert.equal(counter, 0, 'incr was throttled');
+    _.delay(function(){ assert.equal(counter, 0, 'incr was throttled'); done(); }, 64);
+  });
+
+  QUnit.test('debounce', function(assert) {
+    assert.expect(1);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var debouncedIncr = _.debounce(incr, 32);
+    debouncedIncr(); debouncedIncr();
+    _.delay(debouncedIncr, 16);
+    _.delay(function(){ assert.equal(counter, 1, 'incr was debounced'); done(); }, 96);
+  });
+
+  QUnit.test('debounce cancel', function(assert) {
+    assert.expect(1);
+    var done = assert.async();
+    var counter = 0;
+    var incr = function(){ counter++; };
+    var debouncedIncr = _.debounce(incr, 32);
+    debouncedIncr();
+    debouncedIncr.cancel();
+    _.delay(function(){ assert.equal(counter, 0, 'incr was not called'); done(); }, 96);
+  });
+
+  QUnit.test('debounce asap', function(assert) {
+    assert.expect(6);
+    var done = assert.async();
+    var a, b, c;
+    var counter = 0;
+    var incr = function(){ return ++counter; };
+    var debouncedIncr = _.debounce(incr, 64, true);
+    a = debouncedIncr();
+    b = debouncedIncr();
+    assert.equal(a, 1);
+    assert.equal(b, 1);
+    assert.equal(counter, 1, 'incr was called immediately');
+    _.delay(debouncedIncr, 16);
+    _.delay(debouncedIncr, 32);
+    _.delay(debouncedIncr, 48);
+    _.delay(function(){
+      assert.equal(counter, 1, 'incr was debounced');
+      c = debouncedIncr();
+      assert.equal(c, 2);
+      assert.equal(counter, 2, 'incr was called again');
+      done();
+    }, 128);
+  });
+
+  QUnit.test('debounce asap cancel', function(assert) {
+    assert.expect(4);
+    var done = assert.async();
+    var a, b;
+    var counter = 0;
+    var incr = function(){ return ++counter; };
+    var debouncedIncr = _.debounce(incr, 64, true);
+    a = debouncedIncr();
+    debouncedIncr.cancel();
+    b = debouncedIncr();
+    assert.equal(a, 1);
+    assert.equal(b, 2);
+    assert.equal(counter, 2, 'incr was called immediately');
+    _.delay(debouncedIncr, 16);
+    _.delay(debouncedIncr, 32);
+    _.delay(debouncedIncr, 48);
+    _.delay(function(){ assert.equal(counter, 2, 'incr was debounced'); done(); }, 128);
+  });
+
+  QUnit.test('debounce asap recursively', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var debouncedIncr = _.debounce(function(){
+      counter++;
+      if (counter < 10) debouncedIncr();
+    }, 32, true);
+    debouncedIncr();
+    assert.equal(counter, 1, 'incr was called immediately');
+    _.delay(function(){ assert.equal(counter, 1, 'incr was debounced'); done(); }, 96);
+  });
+
+  QUnit.test('debounce after system time is set backwards', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var counter = 0;
+    var origNowFunc = _.now;
+    var debouncedIncr = _.debounce(function(){
+      counter++;
+    }, 100, true);
+
+    debouncedIncr();
+    assert.equal(counter, 1, 'incr was called immediately');
+
+    _.now = function() {
+      return new Date(2013, 0, 1, 1, 1, 1);
+    };
+
+    _.delay(function() {
+      debouncedIncr();
+      assert.equal(counter, 2, 'incr was debounced successfully');
+      done();
+      _.now = origNowFunc;
+    }, 200);
+  });
+
+  QUnit.test('debounce re-entrant', function(assert) {
+    assert.expect(2);
+    var done = assert.async();
+    var sequence = [
+      ['b1', 'b2']
+    ];
+    var value = '';
+    var debouncedAppend;
+    var append = function(arg){
+      value += this + arg;
+      var args = sequence.pop();
+      if (args) {
+        debouncedAppend.call(args[0], args[1]);
+      }
+    };
+    debouncedAppend = _.debounce(append, 32);
+    debouncedAppend.call('a1', 'a2');
+    assert.equal(value, '');
+    _.delay(function(){
+      assert.equal(value, 'a1a2b1b2', 'append was debounced successfully');
+      done();
+    }, 100);
+  });
+
+  QUnit.test('once', function(assert) {
+    var num = 0;
+    var increment = _.once(function(){ return ++num; });
+    increment();
+    increment();
+    assert.equal(num, 1);
+
+    assert.equal(increment(), 1, 'stores a memo to the last value');
+  });
+
+  QUnit.test('Recursive onced function.', function(assert) {
+    assert.expect(1);
+    var f = _.once(function(){
+      assert.ok(true);
+      f();
+    });
+    f();
+  });
+
+  QUnit.test('wrap', function(assert) {
+    var greet = function(name){ return 'hi: ' + name; };
+    var backwards = _.wrap(greet, function(func, name){ return func(name) + ' ' + name.split('').reverse().join(''); });
+    assert.equal(backwards('moe'), 'hi: moe eom', 'wrapped the salutation function');
+
+    var inner = function(){ return 'Hello '; };
+    var obj = {name: 'Moe'};
+    obj.hi = _.wrap(inner, function(fn){ return fn() + this.name; });
+    assert.equal(obj.hi(), 'Hello Moe');
+
+    var noop = function(){};
+    var wrapped = _.wrap(noop, function(){ return Array.prototype.slice.call(arguments, 0); });
+    var ret = wrapped(['whats', 'your'], 'vector', 'victor');
+    assert.deepEqual(ret, [noop, ['whats', 'your'], 'vector', 'victor']);
+  });
+
+  QUnit.test('negate', function(assert) {
+    var isOdd = function(n){ return n & 1; };
+    assert.equal(_.negate(isOdd)(2), true, 'should return the complement of the given function');
+    assert.equal(_.negate(isOdd)(3), false, 'should return the complement of the given function');
+  });
+
+  QUnit.test('compose', function(assert) {
+    var greet = function(name){ return 'hi: ' + name; };
+    var exclaim = function(sentence){ return sentence + '!'; };
+    var composed = _.compose(exclaim, greet);
+    assert.equal(composed('moe'), 'hi: moe!', 'can compose a function that takes another');
+
+    composed = _.compose(greet, exclaim);
+    assert.equal(composed('moe'), 'hi: moe!', 'in this case, the functions are also commutative');
+
+    // f(g(h(x, y, z)))
+    function h(x, y, z) {
+      assert.equal(arguments.length, 3, 'First function called with multiple args');
+      return z * y;
+    }
+    function g(x) {
+      assert.equal(arguments.length, 1, 'Composed function is called with 1 argument');
+      return x;
+    }
+    function f(x) {
+      assert.equal(arguments.length, 1, 'Composed function is called with 1 argument');
+      return x * 2;
+    }
+    composed = _.compose(f, g, h);
+    assert.equal(composed(1, 2, 3), 12);
+  });
+
+  QUnit.test('after', function(assert) {
+    var testAfter = function(afterAmount, timesCalled) {
+      var afterCalled = 0;
+      var after = _.after(afterAmount, function() {
+        afterCalled++;
+      });
+      while (timesCalled--) after();
+      return afterCalled;
+    };
+
+    assert.equal(testAfter(5, 5), 1, 'after(N) should fire after being called N times');
+    assert.equal(testAfter(5, 4), 0, 'after(N) should not fire unless called N times');
+    assert.equal(testAfter(0, 0), 0, 'after(0) should not fire immediately');
+    assert.equal(testAfter(0, 1), 1, 'after(0) should fire when first invoked');
+  });
+
+  QUnit.test('before', function(assert) {
+    var testBefore = function(beforeAmount, timesCalled) {
+      var beforeCalled = 0;
+      var before = _.before(beforeAmount, function() { beforeCalled++; });
+      while (timesCalled--) before();
+      return beforeCalled;
+    };
+
+    assert.equal(testBefore(5, 5), 4, 'before(N) should not fire after being called N times');
+    assert.equal(testBefore(5, 4), 4, 'before(N) should fire before being called N times');
+    assert.equal(testBefore(0, 0), 0, 'before(0) should not fire immediately');
+    assert.equal(testBefore(0, 1), 0, 'before(0) should not fire when first invoked');
+
+    var context = {num: 0};
+    var increment = _.before(3, function(){ return ++this.num; });
+    _.times(10, increment, context);
+    assert.equal(increment(), 2, 'stores a memo to the last value');
+    assert.equal(context.num, 2, 'provides context');
+  });
+
+  QUnit.test('iteratee', function(assert) {
+    var identity = _.iteratee();
+    assert.equal(identity, _.identity, '_.iteratee is exposed as an external function.');
+
+    function fn() {
+      return arguments;
+    }
+    _.each([_.iteratee(fn), _.iteratee(fn, {})], function(cb) {
+      assert.equal(cb().length, 0);
+      assert.deepEqual(_.toArray(cb(1, 2, 3)), _.range(1, 4));
+      assert.deepEqual(_.toArray(cb(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)), _.range(1, 11));
+    });
+
+  });
+
+  QUnit.test('restArgs', function(assert) {
+    assert.expect(10);
+    _.restArgs(function(a, args) {
+      assert.strictEqual(a, 1);
+      assert.deepEqual(args, [2, 3], 'collects rest arguments into an array');
+    })(1, 2, 3);
+
+    _.restArgs(function(a, args) {
+      assert.strictEqual(a, void 0);
+      assert.deepEqual(args, [], 'passes empty array if there are not enough arguments');
+    })();
+
+    _.restArgs(function(a, b, c, args) {
+      assert.strictEqual(arguments.length, 4);
+      assert.deepEqual(args, [4, 5], 'works on functions with many named parameters');
+    })(1, 2, 3, 4, 5);
+
+    var obj = {};
+    _.restArgs(function() {
+      assert.strictEqual(this, obj, 'invokes function with this context');
+    }).call(obj);
+
+    _.restArgs(function(array, iteratee, context) {
+      assert.deepEqual(array, [1, 2, 3, 4], 'startIndex can be used manually specify index of rest parameter');
+      assert.strictEqual(iteratee, void 0);
+      assert.strictEqual(context, void 0);
+    }, 0)(1, 2, 3, 4);
+  });
+
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/objects.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/objects.js
new file mode 100644
index 0000000..fa1d9e3
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/objects.js
@@ -0,0 +1,1102 @@
+(function() {
+  var _ = typeof require == 'function' ? require('..') : window._;
+
+  QUnit.module('Objects');
+
+  var testElement = typeof document === 'object' ? document.createElement('div') : void 0;
+
+  QUnit.test('keys', function(assert) {
+    assert.deepEqual(_.keys({one: 1, two: 2}), ['one', 'two'], 'can extract the keys from an object');
+    // the test above is not safe because it relies on for-in enumeration order
+    var a = []; a[1] = 0;
+    assert.deepEqual(_.keys(a), ['1'], 'is not fooled by sparse arrays; see issue #95');
+    assert.deepEqual(_.keys(null), []);
+    assert.deepEqual(_.keys(void 0), []);
+    assert.deepEqual(_.keys(1), []);
+    assert.deepEqual(_.keys('a'), []);
+    assert.deepEqual(_.keys(true), []);
+
+    // keys that may be missed if the implementation isn't careful
+    var trouble = {
+      constructor: Object,
+      valueOf: _.noop,
+      hasOwnProperty: null,
+      toString: 5,
+      toLocaleString: void 0,
+      propertyIsEnumerable: /a/,
+      isPrototypeOf: this,
+      __defineGetter__: Boolean,
+      __defineSetter__: {},
+      __lookupSetter__: false,
+      __lookupGetter__: []
+    };
+    var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable',
+                  'isPrototypeOf', '__defineGetter__', '__defineSetter__', '__lookupSetter__', '__lookupGetter__'].sort();
+    assert.deepEqual(_.keys(trouble).sort(), troubleKeys, 'matches non-enumerable properties');
+  });
+
+  QUnit.test('allKeys', function(assert) {
+    assert.deepEqual(_.allKeys({one: 1, two: 2}), ['one', 'two'], 'can extract the allKeys from an object');
+    // the test above is not safe because it relies on for-in enumeration order
+    var a = []; a[1] = 0;
+    assert.deepEqual(_.allKeys(a), ['1'], 'is not fooled by sparse arrays; see issue #95');
+
+    a.a = a;
+    assert.deepEqual(_.allKeys(a), ['1', 'a'], 'is not fooled by sparse arrays with additional properties');
+
+    _.each([null, void 0, 1, 'a', true, NaN, {}, [], new Number(5), new Date(0)], function(val) {
+      assert.deepEqual(_.allKeys(val), []);
+    });
+
+    // allKeys that may be missed if the implementation isn't careful
+    var trouble = {
+      constructor: Object,
+      valueOf: _.noop,
+      hasOwnProperty: null,
+      toString: 5,
+      toLocaleString: void 0,
+      propertyIsEnumerable: /a/,
+      isPrototypeOf: this
+    };
+    var troubleKeys = ['constructor', 'valueOf', 'hasOwnProperty', 'toString', 'toLocaleString', 'propertyIsEnumerable',
+                  'isPrototypeOf'].sort();
+    assert.deepEqual(_.allKeys(trouble).sort(), troubleKeys, 'matches non-enumerable properties');
+
+    function A() {}
+    A.prototype.foo = 'foo';
+    var b = new A();
+    b.bar = 'bar';
+    assert.deepEqual(_.allKeys(b).sort(), ['bar', 'foo'], 'should include inherited keys');
+
+    function y() {}
+    y.x = 'z';
+    assert.deepEqual(_.allKeys(y), ['x'], 'should get keys from constructor');
+  });
+
+  QUnit.test('values', function(assert) {
+    assert.deepEqual(_.values({one: 1, two: 2}), [1, 2], 'can extract the values from an object');
+    assert.deepEqual(_.values({one: 1, two: 2, length: 3}), [1, 2, 3], '... even when one of them is "length"');
+  });
+
+  QUnit.test('pairs', function(assert) {
+    assert.deepEqual(_.pairs({one: 1, two: 2}), [['one', 1], ['two', 2]], 'can convert an object into pairs');
+    assert.deepEqual(_.pairs({one: 1, two: 2, length: 3}), [['one', 1], ['two', 2], ['length', 3]], '... even when one of them is "length"');
+  });
+
+  QUnit.test('invert', function(assert) {
+    var obj = {first: 'Moe', second: 'Larry', third: 'Curly'};
+    assert.deepEqual(_.keys(_.invert(obj)), ['Moe', 'Larry', 'Curly'], 'can invert an object');
+    assert.deepEqual(_.invert(_.invert(obj)), obj, 'two inverts gets you back where you started');
+
+    obj = {length: 3};
+    assert.equal(_.invert(obj)['3'], 'length', 'can invert an object with "length"');
+  });
+
+  QUnit.test('functions', function(assert) {
+    var obj = {a: 'dash', b: _.map, c: /yo/, d: _.reduce};
+    assert.deepEqual(['b', 'd'], _.functions(obj), 'can grab the function names of any passed-in object');
+
+    var Animal = function(){};
+    Animal.prototype.run = function(){};
+    assert.deepEqual(_.functions(new Animal), ['run'], 'also looks up functions on the prototype');
+  });
+
+  QUnit.test('methods', function(assert) {
+    assert.strictEqual(_.methods, _.functions, 'is an alias for functions');
+  });
+
+  QUnit.test('extend', function(assert) {
+    var result;
+    assert.equal(_.extend({}, {a: 'b'}).a, 'b', 'can extend an object with the attributes of another');
+    assert.equal(_.extend({a: 'x'}, {a: 'b'}).a, 'b', 'properties in source override destination');
+    assert.equal(_.extend({x: 'x'}, {a: 'b'}).x, 'x', "properties not in source don't get overriden");
+    result = _.extend({x: 'x'}, {a: 'a'}, {b: 'b'});
+    assert.deepEqual(result, {x: 'x', a: 'a', b: 'b'}, 'can extend from multiple source objects');
+    result = _.extend({x: 'x'}, {a: 'a', x: 2}, {a: 'b'});
+    assert.deepEqual(result, {x: 2, a: 'b'}, 'extending from multiple source objects last property trumps');
+    result = _.extend({}, {a: void 0, b: null});
+    assert.deepEqual(_.keys(result), ['a', 'b'], 'extend copies undefined values');
+
+    var F = function() {};
+    F.prototype = {a: 'b'};
+    var subObj = new F();
+    subObj.c = 'd';
+    assert.deepEqual(_.extend({}, subObj), {a: 'b', c: 'd'}, 'extend copies all properties from source');
+    _.extend(subObj, {});
+    assert.ok(!subObj.hasOwnProperty('a'), "extend does not convert destination object's 'in' properties to 'own' properties");
+
+    try {
+      result = {};
+      _.extend(result, null, void 0, {a: 1});
+    } catch (e) { /* ignored */ }
+
+    assert.equal(result.a, 1, 'should not error on `null` or `undefined` sources');
+
+    assert.strictEqual(_.extend(null, {a: 1}), null, 'extending null results in null');
+    assert.strictEqual(_.extend(void 0, {a: 1}), void 0, 'extending undefined results in undefined');
+  });
+
+  QUnit.test('extendOwn', function(assert) {
+    var result;
+    assert.equal(_.extendOwn({}, {a: 'b'}).a, 'b', 'can extend an object with the attributes of another');
+    assert.equal(_.extendOwn({a: 'x'}, {a: 'b'}).a, 'b', 'properties in source override destination');
+    assert.equal(_.extendOwn({x: 'x'}, {a: 'b'}).x, 'x', "properties not in source don't get overriden");
+    result = _.extendOwn({x: 'x'}, {a: 'a'}, {b: 'b'});
+    assert.deepEqual(result, {x: 'x', a: 'a', b: 'b'}, 'can extend from multiple source objects');
+    result = _.extendOwn({x: 'x'}, {a: 'a', x: 2}, {a: 'b'});
+    assert.deepEqual(result, {x: 2, a: 'b'}, 'extending from multiple source objects last property trumps');
+    assert.deepEqual(_.extendOwn({}, {a: void 0, b: null}), {a: void 0, b: null}, 'copies undefined values');
+
+    var F = function() {};
+    F.prototype = {a: 'b'};
+    var subObj = new F();
+    subObj.c = 'd';
+    assert.deepEqual(_.extendOwn({}, subObj), {c: 'd'}, 'copies own properties from source');
+
+    result = {};
+    assert.deepEqual(_.extendOwn(result, null, void 0, {a: 1}), {a: 1}, 'should not error on `null` or `undefined` sources');
+
+    _.each(['a', 5, null, false], function(val) {
+      assert.strictEqual(_.extendOwn(val, {a: 1}), val, 'extending non-objects results in returning the non-object value');
+    });
+
+    assert.strictEqual(_.extendOwn(void 0, {a: 1}), void 0, 'extending undefined results in undefined');
+
+    result = _.extendOwn({a: 1, 0: 2, 1: '5', length: 6}, {0: 1, 1: 2, length: 2});
+    assert.deepEqual(result, {a: 1, 0: 1, 1: 2, length: 2}, 'should treat array-like objects like normal objects');
+  });
+
+  QUnit.test('assign', function(assert) {
+    assert.strictEqual(_.assign, _.extendOwn, 'is an alias for extendOwn');
+  });
+
+  QUnit.test('pick', function(assert) {
+    var result;
+    result = _.pick({a: 1, b: 2, c: 3}, 'a', 'c');
+    assert.deepEqual(result, {a: 1, c: 3}, 'can restrict properties to those named');
+    result = _.pick({a: 1, b: 2, c: 3}, ['b', 'c']);
+    assert.deepEqual(result, {b: 2, c: 3}, 'can restrict properties to those named in an array');
+    result = _.pick({a: 1, b: 2, c: 3}, ['a'], 'b');
+    assert.deepEqual(result, {a: 1, b: 2}, 'can restrict properties to those named in mixed args');
+    result = _.pick(['a', 'b'], 1);
+    assert.deepEqual(result, {1: 'b'}, 'can pick numeric properties');
+
+    _.each([null, void 0], function(val) {
+      assert.deepEqual(_.pick(val, 'hasOwnProperty'), {}, 'Called with null/undefined');
+      assert.deepEqual(_.pick(val, _.constant(true)), {});
+    });
+    assert.deepEqual(_.pick(5, 'toString', 'b'), {toString: Number.prototype.toString}, 'can iterate primitives');
+
+    var data = {a: 1, b: 2, c: 3};
+    var callback = function(value, key, object) {
+      assert.strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
+      assert.strictEqual(object, data);
+      return value !== this.value;
+    };
+    result = _.pick(data, callback, {value: 2});
+    assert.deepEqual(result, {a: 1, c: 3}, 'can accept a predicate and context');
+
+    var Obj = function(){};
+    Obj.prototype = {a: 1, b: 2, c: 3};
+    var instance = new Obj();
+    assert.deepEqual(_.pick(instance, 'a', 'c'), {a: 1, c: 3}, 'include prototype props');
+
+    assert.deepEqual(_.pick(data, function(val, key) {
+      return this[key] === 3 && this === instance;
+    }, instance), {c: 3}, 'function is given context');
+
+    assert.ok(!_.has(_.pick({}, 'foo'), 'foo'), 'does not set own property if property not in object');
+    _.pick(data, function(value, key, obj) {
+      assert.equal(obj, data, 'passes same object as third parameter of iteratee');
+    });
+  });
+
+  QUnit.test('omit', function(assert) {
+    var result;
+    result = _.omit({a: 1, b: 2, c: 3}, 'b');
+    assert.deepEqual(result, {a: 1, c: 3}, 'can omit a single named property');
+    result = _.omit({a: 1, b: 2, c: 3}, 'a', 'c');
+    assert.deepEqual(result, {b: 2}, 'can omit several named properties');
+    result = _.omit({a: 1, b: 2, c: 3}, ['b', 'c']);
+    assert.deepEqual(result, {a: 1}, 'can omit properties named in an array');
+    result = _.omit(['a', 'b'], 0);
+    assert.deepEqual(result, {1: 'b'}, 'can omit numeric properties');
+
+    assert.deepEqual(_.omit(null, 'a', 'b'), {}, 'non objects return empty object');
+    assert.deepEqual(_.omit(void 0, 'toString'), {}, 'null/undefined return empty object');
+    assert.deepEqual(_.omit(5, 'toString', 'b'), {}, 'returns empty object for primitives');
+
+    var data = {a: 1, b: 2, c: 3};
+    var callback = function(value, key, object) {
+      assert.strictEqual(key, {1: 'a', 2: 'b', 3: 'c'}[value]);
+      assert.strictEqual(object, data);
+      return value !== this.value;
+    };
+    result = _.omit(data, callback, {value: 2});
+    assert.deepEqual(result, {b: 2}, 'can accept a predicate');
+
+    var Obj = function(){};
+    Obj.prototype = {a: 1, b: 2, c: 3};
+    var instance = new Obj();
+    assert.deepEqual(_.omit(instance, 'b'), {a: 1, c: 3}, 'include prototype props');
+
+    assert.deepEqual(_.omit(data, function(val, key) {
+      return this[key] === 3 && this === instance;
+    }, instance), {a: 1, b: 2}, 'function is given context');
+  });
+
+  QUnit.test('defaults', function(assert) {
+    var options = {zero: 0, one: 1, empty: '', nan: NaN, nothing: null};
+
+    _.defaults(options, {zero: 1, one: 10, twenty: 20, nothing: 'str'});
+    assert.equal(options.zero, 0, 'value exists');
+    assert.equal(options.one, 1, 'value exists');
+    assert.equal(options.twenty, 20, 'default applied');
+    assert.equal(options.nothing, null, "null isn't overridden");
+
+    _.defaults(options, {empty: 'full'}, {nan: 'nan'}, {word: 'word'}, {word: 'dog'});
+    assert.equal(options.empty, '', 'value exists');
+    assert.ok(_.isNaN(options.nan), "NaN isn't overridden");
+    assert.equal(options.word, 'word', 'new value is added, first one wins');
+
+    try {
+      options = {};
+      _.defaults(options, null, void 0, {a: 1});
+    } catch (e) { /* ignored */ }
+
+    assert.equal(options.a, 1, 'should not error on `null` or `undefined` sources');
+
+    assert.deepEqual(_.defaults(null, {a: 1}), {a: 1}, 'defaults skips nulls');
+    assert.deepEqual(_.defaults(void 0, {a: 1}), {a: 1}, 'defaults skips undefined');
+  });
+
+  QUnit.test('clone', function(assert) {
+    var moe = {name: 'moe', lucky: [13, 27, 34]};
+    var clone = _.clone(moe);
+    assert.equal(clone.name, 'moe', 'the clone as the attributes of the original');
+
+    clone.name = 'curly';
+    assert.ok(clone.name === 'curly' && moe.name === 'moe', 'clones can change shallow attributes without affecting the original');
+
+    clone.lucky.push(101);
+    assert.equal(_.last(moe.lucky), 101, 'changes to deep attributes are shared with the original');
+
+    assert.equal(_.clone(void 0), void 0, 'non objects should not be changed by clone');
+    assert.equal(_.clone(1), 1, 'non objects should not be changed by clone');
+    assert.equal(_.clone(null), null, 'non objects should not be changed by clone');
+  });
+
+  QUnit.test('create', function(assert) {
+    var Parent = function() {};
+    Parent.prototype = {foo: function() {}, bar: 2};
+
+    _.each(['foo', null, void 0, 1], function(val) {
+      assert.deepEqual(_.create(val), {}, 'should return empty object when a non-object is provided');
+    });
+
+    assert.ok(_.create([]) instanceof Array, 'should return new instance of array when array is provided');
+
+    var Child = function() {};
+    Child.prototype = _.create(Parent.prototype);
+    assert.ok(new Child instanceof Parent, 'object should inherit prototype');
+
+    var func = function() {};
+    Child.prototype = _.create(Parent.prototype, {func: func});
+    assert.strictEqual(Child.prototype.func, func, 'properties should be added to object');
+
+    Child.prototype = _.create(Parent.prototype, {constructor: Child});
+    assert.strictEqual(Child.prototype.constructor, Child);
+
+    Child.prototype.foo = 'foo';
+    var created = _.create(Child.prototype, new Child);
+    assert.ok(!created.hasOwnProperty('foo'), 'should only add own properties');
+  });
+
+  QUnit.test('isEqual', function(assert) {
+    function First() {
+      this.value = 1;
+    }
+    First.prototype.value = 1;
+    function Second() {
+      this.value = 1;
+    }
+    Second.prototype.value = 2;
+
+    // Basic equality and identity comparisons.
+    assert.ok(_.isEqual(null, null), '`null` is equal to `null`');
+    assert.ok(_.isEqual(), '`undefined` is equal to `undefined`');
+
+    assert.ok(!_.isEqual(0, -0), '`0` is not equal to `-0`');
+    assert.ok(!_.isEqual(-0, 0), 'Commutative equality is implemented for `0` and `-0`');
+    assert.ok(!_.isEqual(null, void 0), '`null` is not equal to `undefined`');
+    assert.ok(!_.isEqual(void 0, null), 'Commutative equality is implemented for `null` and `undefined`');
+
+    // String object and primitive comparisons.
+    assert.ok(_.isEqual('Curly', 'Curly'), 'Identical string primitives are equal');
+    assert.ok(_.isEqual(new String('Curly'), new String('Curly')), 'String objects with identical primitive values are equal');
+    assert.ok(_.isEqual(new String('Curly'), 'Curly'), 'String primitives and their corresponding object wrappers are equal');
+    assert.ok(_.isEqual('Curly', new String('Curly')), 'Commutative equality is implemented for string objects and primitives');
+
+    assert.ok(!_.isEqual('Curly', 'Larry'), 'String primitives with different values are not equal');
+    assert.ok(!_.isEqual(new String('Curly'), new String('Larry')), 'String objects with different primitive values are not equal');
+    assert.ok(!_.isEqual(new String('Curly'), {toString: function(){ return 'Curly'; }}), 'String objects and objects with a custom `toString` method are not equal');
+
+    // Number object and primitive comparisons.
+    assert.ok(_.isEqual(75, 75), 'Identical number primitives are equal');
+    assert.ok(_.isEqual(new Number(75), new Number(75)), 'Number objects with identical primitive values are equal');
+    assert.ok(_.isEqual(75, new Number(75)), 'Number primitives and their corresponding object wrappers are equal');
+    assert.ok(_.isEqual(new Number(75), 75), 'Commutative equality is implemented for number objects and primitives');
+    assert.ok(!_.isEqual(new Number(0), -0), '`new Number(0)` and `-0` are not equal');
+    assert.ok(!_.isEqual(0, new Number(-0)), 'Commutative equality is implemented for `new Number(0)` and `-0`');
+
+    assert.ok(!_.isEqual(new Number(75), new Number(63)), 'Number objects with different primitive values are not equal');
+    assert.ok(!_.isEqual(new Number(63), {valueOf: function(){ return 63; }}), 'Number objects and objects with a `valueOf` method are not equal');
+
+    // Comparisons involving `NaN`.
+    assert.ok(_.isEqual(NaN, NaN), '`NaN` is equal to `NaN`');
+    assert.ok(_.isEqual(new Number(NaN), NaN), 'Object(`NaN`) is equal to `NaN`');
+    assert.ok(!_.isEqual(61, NaN), 'A number primitive is not equal to `NaN`');
+    assert.ok(!_.isEqual(new Number(79), NaN), 'A number object is not equal to `NaN`');
+    assert.ok(!_.isEqual(Infinity, NaN), '`Infinity` is not equal to `NaN`');
+
+    // Boolean object and primitive comparisons.
+    assert.ok(_.isEqual(true, true), 'Identical boolean primitives are equal');
+    assert.ok(_.isEqual(new Boolean, new Boolean), 'Boolean objects with identical primitive values are equal');
+    assert.ok(_.isEqual(true, new Boolean(true)), 'Boolean primitives and their corresponding object wrappers are equal');
+    assert.ok(_.isEqual(new Boolean(true), true), 'Commutative equality is implemented for booleans');
+    assert.ok(!_.isEqual(new Boolean(true), new Boolean), 'Boolean objects with different primitive values are not equal');
+
+    // Common type coercions.
+    assert.ok(!_.isEqual(new Boolean(false), true), '`new Boolean(false)` is not equal to `true`');
+    assert.ok(!_.isEqual('75', 75), 'String and number primitives with like values are not equal');
+    assert.ok(!_.isEqual(new Number(63), new String(63)), 'String and number objects with like values are not equal');
+    assert.ok(!_.isEqual(75, '75'), 'Commutative equality is implemented for like string and number values');
+    assert.ok(!_.isEqual(0, ''), 'Number and string primitives with like values are not equal');
+    assert.ok(!_.isEqual(1, true), 'Number and boolean primitives with like values are not equal');
+    assert.ok(!_.isEqual(new Boolean(false), new Number(0)), 'Boolean and number objects with like values are not equal');
+    assert.ok(!_.isEqual(false, new String('')), 'Boolean primitives and string objects with like values are not equal');
+    assert.ok(!_.isEqual(12564504e5, new Date(2009, 9, 25)), 'Dates and their corresponding numeric primitive values are not equal');
+
+    // Dates.
+    assert.ok(_.isEqual(new Date(2009, 9, 25), new Date(2009, 9, 25)), 'Date objects referencing identical times are equal');
+    assert.ok(!_.isEqual(new Date(2009, 9, 25), new Date(2009, 11, 13)), 'Date objects referencing different times are not equal');
+    assert.ok(!_.isEqual(new Date(2009, 11, 13), {
+      getTime: function(){
+        return 12606876e5;
+      }
+    }), 'Date objects and objects with a `getTime` method are not equal');
+    assert.ok(!_.isEqual(new Date('Curly'), new Date('Curly')), 'Invalid dates are not equal');
+
+    // Functions.
+    assert.ok(!_.isEqual(First, Second), 'Different functions with identical bodies and source code representations are not equal');
+
+    // RegExps.
+    assert.ok(_.isEqual(/(?:)/gim, /(?:)/gim), 'RegExps with equivalent patterns and flags are equal');
+    assert.ok(_.isEqual(/(?:)/gi, /(?:)/ig), 'Flag order is not significant');
+    assert.ok(!_.isEqual(/(?:)/g, /(?:)/gi), 'RegExps with equivalent patterns and different flags are not equal');
+    assert.ok(!_.isEqual(/Moe/gim, /Curly/gim), 'RegExps with different patterns and equivalent flags are not equal');
+    assert.ok(!_.isEqual(/(?:)/gi, /(?:)/g), 'Commutative equality is implemented for RegExps');
+    assert.ok(!_.isEqual(/Curly/g, {source: 'Larry', global: true, ignoreCase: false, multiline: false}), 'RegExps and RegExp-like objects are not equal');
+
+    // Empty arrays, array-like objects, and object literals.
+    assert.ok(_.isEqual({}, {}), 'Empty object literals are equal');
+    assert.ok(_.isEqual([], []), 'Empty array literals are equal');
+    assert.ok(_.isEqual([{}], [{}]), 'Empty nested arrays and objects are equal');
+    assert.ok(!_.isEqual({length: 0}, []), 'Array-like objects and arrays are not equal.');
+    assert.ok(!_.isEqual([], {length: 0}), 'Commutative equality is implemented for array-like objects');
+
+    assert.ok(!_.isEqual({}, []), 'Object literals and array literals are not equal');
+    assert.ok(!_.isEqual([], {}), 'Commutative equality is implemented for objects and arrays');
+
+    // Arrays with primitive and object values.
+    assert.ok(_.isEqual([1, 'Larry', true], [1, 'Larry', true]), 'Arrays containing identical primitives are equal');
+    assert.ok(_.isEqual([/Moe/g, new Date(2009, 9, 25)], [/Moe/g, new Date(2009, 9, 25)]), 'Arrays containing equivalent elements are equal');
+
+    // Multi-dimensional arrays.
+    var a = [new Number(47), false, 'Larry', /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
+    var b = [new Number(47), false, 'Larry', /Moe/, new Date(2009, 11, 13), ['running', 'biking', new String('programming')], {a: 47}];
+    assert.ok(_.isEqual(a, b), 'Arrays containing nested arrays and objects are recursively compared');
+
+    // Overwrite the methods defined in ES 5.1 section 15.4.4.
+    a.forEach = a.map = a.filter = a.every = a.indexOf = a.lastIndexOf = a.some = a.reduce = a.reduceRight = null;
+    b.join = b.pop = b.reverse = b.shift = b.slice = b.splice = b.concat = b.sort = b.unshift = null;
+
+    // Array elements and properties.
+    assert.ok(_.isEqual(a, b), 'Arrays containing equivalent elements and different non-numeric properties are equal');
+    a.push('White Rocks');
+    assert.ok(!_.isEqual(a, b), 'Arrays of different lengths are not equal');
+    a.push('East Boulder');
+    b.push('Gunbarrel Ranch', 'Teller Farm');
+    assert.ok(!_.isEqual(a, b), 'Arrays of identical lengths containing different elements are not equal');
+
+    // Sparse arrays.
+    assert.ok(_.isEqual(Array(3), Array(3)), 'Sparse arrays of identical lengths are equal');
+    assert.ok(!_.isEqual(Array(3), Array(6)), 'Sparse arrays of different lengths are not equal when both are empty');
+
+    var sparse = [];
+    sparse[1] = 5;
+    assert.ok(_.isEqual(sparse, [void 0, 5]), 'Handles sparse arrays as dense');
+
+    // Simple objects.
+    assert.ok(_.isEqual({a: 'Curly', b: 1, c: true}, {a: 'Curly', b: 1, c: true}), 'Objects containing identical primitives are equal');
+    assert.ok(_.isEqual({a: /Curly/g, b: new Date(2009, 11, 13)}, {a: /Curly/g, b: new Date(2009, 11, 13)}), 'Objects containing equivalent members are equal');
+    assert.ok(!_.isEqual({a: 63, b: 75}, {a: 61, b: 55}), 'Objects of identical sizes with different values are not equal');
+    assert.ok(!_.isEqual({a: 63, b: 75}, {a: 61, c: 55}), 'Objects of identical sizes with different property names are not equal');
+    assert.ok(!_.isEqual({a: 1, b: 2}, {a: 1}), 'Objects of different sizes are not equal');
+    assert.ok(!_.isEqual({a: 1}, {a: 1, b: 2}), 'Commutative equality is implemented for objects');
+    assert.ok(!_.isEqual({x: 1, y: void 0}, {x: 1, z: 2}), 'Objects with identical keys and different values are not equivalent');
+
+    // `A` contains nested objects and arrays.
+    a = {
+      name: new String('Moe Howard'),
+      age: new Number(77),
+      stooge: true,
+      hobbies: ['acting'],
+      film: {
+        name: 'Sing a Song of Six Pants',
+        release: new Date(1947, 9, 30),
+        stars: [new String('Larry Fine'), 'Shemp Howard'],
+        minutes: new Number(16),
+        seconds: 54
+      }
+    };
+
+    // `B` contains equivalent nested objects and arrays.
+    b = {
+      name: new String('Moe Howard'),
+      age: new Number(77),
+      stooge: true,
+      hobbies: ['acting'],
+      film: {
+        name: 'Sing a Song of Six Pants',
+        release: new Date(1947, 9, 30),
+        stars: [new String('Larry Fine'), 'Shemp Howard'],
+        minutes: new Number(16),
+        seconds: 54
+      }
+    };
+    assert.ok(_.isEqual(a, b), 'Objects with nested equivalent members are recursively compared');
+
+    // Instances.
+    assert.ok(_.isEqual(new First, new First), 'Object instances are equal');
+    assert.ok(!_.isEqual(new First, new Second), 'Objects with different constructors and identical own properties are not equal');
+    assert.ok(!_.isEqual({value: 1}, new First), 'Object instances and objects sharing equivalent properties are not equal');
+    assert.ok(!_.isEqual({value: 2}, new Second), 'The prototype chain of objects should not be examined');
+
+    // Circular Arrays.
+    (a = []).push(a);
+    (b = []).push(b);
+    assert.ok(_.isEqual(a, b), 'Arrays containing circular references are equal');
+    a.push(new String('Larry'));
+    b.push(new String('Larry'));
+    assert.ok(_.isEqual(a, b), 'Arrays containing circular references and equivalent properties are equal');
+    a.push('Shemp');
+    b.push('Curly');
+    assert.ok(!_.isEqual(a, b), 'Arrays containing circular references and different properties are not equal');
+
+    // More circular arrays #767.
+    a = ['everything is checked but', 'this', 'is not'];
+    a[1] = a;
+    b = ['everything is checked but', ['this', 'array'], 'is not'];
+    assert.ok(!_.isEqual(a, b), 'Comparison of circular references with non-circular references are not equal');
+
+    // Circular Objects.
+    a = {abc: null};
+    b = {abc: null};
+    a.abc = a;
+    b.abc = b;
+    assert.ok(_.isEqual(a, b), 'Objects containing circular references are equal');
+    a.def = 75;
+    b.def = 75;
+    assert.ok(_.isEqual(a, b), 'Objects containing circular references and equivalent properties are equal');
+    a.def = new Number(75);
+    b.def = new Number(63);
+    assert.ok(!_.isEqual(a, b), 'Objects containing circular references and different properties are not equal');
+
+    // More circular objects #767.
+    a = {everything: 'is checked', but: 'this', is: 'not'};
+    a.but = a;
+    b = {everything: 'is checked', but: {that: 'object'}, is: 'not'};
+    assert.ok(!_.isEqual(a, b), 'Comparison of circular references with non-circular object references are not equal');
+
+    // Cyclic Structures.
+    a = [{abc: null}];
+    b = [{abc: null}];
+    (a[0].abc = a).push(a);
+    (b[0].abc = b).push(b);
+    assert.ok(_.isEqual(a, b), 'Cyclic structures are equal');
+    a[0].def = 'Larry';
+    b[0].def = 'Larry';
+    assert.ok(_.isEqual(a, b), 'Cyclic structures containing equivalent properties are equal');
+    a[0].def = new String('Larry');
+    b[0].def = new String('Curly');
+    assert.ok(!_.isEqual(a, b), 'Cyclic structures containing different properties are not equal');
+
+    // Complex Circular References.
+    a = {foo: {b: {foo: {c: {foo: null}}}}};
+    b = {foo: {b: {foo: {c: {foo: null}}}}};
+    a.foo.b.foo.c.foo = a;
+    b.foo.b.foo.c.foo = b;
+    assert.ok(_.isEqual(a, b), 'Cyclic structures with nested and identically-named properties are equal');
+
+    // Chaining.
+    assert.ok(!_.isEqual(_({x: 1, y: void 0}).chain(), _({x: 1, z: 2}).chain()), 'Chained objects containing different values are not equal');
+
+    a = _({x: 1, y: 2}).chain();
+    b = _({x: 1, y: 2}).chain();
+    assert.equal(_.isEqual(a.isEqual(b), _(true)), true, '`isEqual` can be chained');
+
+    // Objects without a `constructor` property
+    if (Object.create) {
+      a = Object.create(null, {x: {value: 1, enumerable: true}});
+      b = {x: 1};
+      assert.ok(_.isEqual(a, b), 'Handles objects without a constructor (e.g. from Object.create');
+    }
+
+    function Foo() { this.a = 1; }
+    Foo.prototype.constructor = null;
+
+    var other = {a: 1};
+    assert.strictEqual(_.isEqual(new Foo, other), false, 'Objects from different constructors are not equal');
+
+
+    // Tricky object cases val comparisions
+    assert.equal(_.isEqual([0], [-0]), false);
+    assert.equal(_.isEqual({a: 0}, {a: -0}), false);
+    assert.equal(_.isEqual([NaN], [NaN]), true);
+    assert.equal(_.isEqual({a: NaN}, {a: NaN}), true);
+
+    if (typeof Symbol !== 'undefined') {
+      var symbol = Symbol('x');
+      assert.strictEqual(_.isEqual(symbol, symbol), true, 'A symbol is equal to itself');
+      assert.strictEqual(_.isEqual(symbol, Object(symbol)), true, 'Even when wrapped in Object()');
+      assert.strictEqual(_.isEqual(symbol, null), false, 'Different types are not equal');
+    }
+
+  });
+
+  QUnit.test('isEmpty', function(assert) {
+    assert.ok(!_([1]).isEmpty(), '[1] is not empty');
+    assert.ok(_.isEmpty([]), '[] is empty');
+    assert.ok(!_.isEmpty({one: 1}), '{one: 1} is not empty');
+    assert.ok(_.isEmpty({}), '{} is empty');
+    assert.ok(_.isEmpty(new RegExp('')), 'objects with prototype properties are empty');
+    assert.ok(_.isEmpty(null), 'null is empty');
+    assert.ok(_.isEmpty(), 'undefined is empty');
+    assert.ok(_.isEmpty(''), 'the empty string is empty');
+    assert.ok(!_.isEmpty('moe'), 'but other strings are not');
+
+    var obj = {one: 1};
+    delete obj.one;
+    assert.ok(_.isEmpty(obj), 'deleting all the keys from an object empties it');
+
+    var args = function(){ return arguments; };
+    assert.ok(_.isEmpty(args()), 'empty arguments object is empty');
+    assert.ok(!_.isEmpty(args('')), 'non-empty arguments object is not empty');
+
+    // covers collecting non-enumerable properties in IE < 9
+    var nonEnumProp = {toString: 5};
+    assert.ok(!_.isEmpty(nonEnumProp), 'non-enumerable property is not empty');
+  });
+
+  if (typeof document === 'object') {
+    QUnit.test('isElement', function(assert) {
+      assert.ok(!_.isElement('div'), 'strings are not dom elements');
+      assert.ok(_.isElement(testElement), 'an element is a DOM element');
+    });
+  }
+
+  QUnit.test('isArguments', function(assert) {
+    var args = (function(){ return arguments; }(1, 2, 3));
+    assert.ok(!_.isArguments('string'), 'a string is not an arguments object');
+    assert.ok(!_.isArguments(_.isArguments), 'a function is not an arguments object');
+    assert.ok(_.isArguments(args), 'but the arguments object is an arguments object');
+    assert.ok(!_.isArguments(_.toArray(args)), 'but not when it\'s converted into an array');
+    assert.ok(!_.isArguments([1, 2, 3]), 'and not vanilla arrays.');
+  });
+
+  QUnit.test('isObject', function(assert) {
+    assert.ok(_.isObject(arguments), 'the arguments object is object');
+    assert.ok(_.isObject([1, 2, 3]), 'and arrays');
+    if (testElement) {
+      assert.ok(_.isObject(testElement), 'and DOM element');
+    }
+    assert.ok(_.isObject(function() {}), 'and functions');
+    assert.ok(!_.isObject(null), 'but not null');
+    assert.ok(!_.isObject(void 0), 'and not undefined');
+    assert.ok(!_.isObject('string'), 'and not string');
+    assert.ok(!_.isObject(12), 'and not number');
+    assert.ok(!_.isObject(true), 'and not boolean');
+    assert.ok(_.isObject(new String('string')), 'but new String()');
+  });
+
+  QUnit.test('isArray', function(assert) {
+    assert.ok(!_.isArray(void 0), 'undefined vars are not arrays');
+    assert.ok(!_.isArray(arguments), 'the arguments object is not an array');
+    assert.ok(_.isArray([1, 2, 3]), 'but arrays are');
+  });
+
+  QUnit.test('isString', function(assert) {
+    var obj = new String('I am a string object');
+    if (testElement) {
+      assert.ok(!_.isString(testElement), 'an element is not a string');
+    }
+    assert.ok(_.isString([1, 2, 3].join(', ')), 'but strings are');
+    assert.strictEqual(_.isString('I am a string literal'), true, 'string literals are');
+    assert.ok(_.isString(obj), 'so are String objects');
+    assert.strictEqual(_.isString(1), false);
+  });
+
+  QUnit.test('isSymbol', function(assert) {
+    assert.ok(!_.isSymbol(0), 'numbers are not symbols');
+    assert.ok(!_.isSymbol(''), 'strings are not symbols');
+    assert.ok(!_.isSymbol(_.isSymbol), 'functions are not symbols');
+    if (typeof Symbol === 'function') {
+      assert.ok(_.isSymbol(Symbol()), 'symbols are symbols');
+      assert.ok(_.isSymbol(Symbol('description')), 'described symbols are symbols');
+      assert.ok(_.isSymbol(Object(Symbol())), 'boxed symbols are symbols');
+    }
+  });
+
+  QUnit.test('isNumber', function(assert) {
+    assert.ok(!_.isNumber('string'), 'a string is not a number');
+    assert.ok(!_.isNumber(arguments), 'the arguments object is not a number');
+    assert.ok(!_.isNumber(void 0), 'undefined is not a number');
+    assert.ok(_.isNumber(3 * 4 - 7 / 10), 'but numbers are');
+    assert.ok(_.isNumber(NaN), 'NaN *is* a number');
+    assert.ok(_.isNumber(Infinity), 'Infinity is a number');
+    assert.ok(!_.isNumber('1'), 'numeric strings are not numbers');
+  });
+
+  QUnit.test('isBoolean', function(assert) {
+    assert.ok(!_.isBoolean(2), 'a number is not a boolean');
+    assert.ok(!_.isBoolean('string'), 'a string is not a boolean');
+    assert.ok(!_.isBoolean('false'), 'the string "false" is not a boolean');
+    assert.ok(!_.isBoolean('true'), 'the string "true" is not a boolean');
+    assert.ok(!_.isBoolean(arguments), 'the arguments object is not a boolean');
+    assert.ok(!_.isBoolean(void 0), 'undefined is not a boolean');
+    assert.ok(!_.isBoolean(NaN), 'NaN is not a boolean');
+    assert.ok(!_.isBoolean(null), 'null is not a boolean');
+    assert.ok(_.isBoolean(true), 'but true is');
+    assert.ok(_.isBoolean(false), 'and so is false');
+  });
+
+  QUnit.test('isMap', function(assert) {
+    assert.ok(!_.isMap('string'), 'a string is not a map');
+    assert.ok(!_.isMap(2), 'a number is not a map');
+    assert.ok(!_.isMap({}), 'an object is not a map');
+    assert.ok(!_.isMap(false), 'a boolean is not a map');
+    assert.ok(!_.isMap(void 0), 'undefined is not a map');
+    assert.ok(!_.isMap([1, 2, 3]), 'an array is not a map');
+    if (typeof Set === 'function') {
+      assert.ok(!_.isMap(new Set()), 'a set is not a map');
+    }
+    if (typeof WeakSet === 'function') {
+      assert.ok(!_.isMap(new WeakSet()), 'a weakset is not a map');
+    }
+    if (typeof WeakMap === 'function') {
+      assert.ok(!_.isMap(new WeakMap()), 'a weakmap is not a map');
+    }
+    if (typeof Map === 'function') {
+      var keyString = 'a string';
+      var obj = new Map();
+      obj.set(keyString, 'value');
+      assert.ok(_.isMap(obj), 'but a map is');
+    }
+  });
+
+  QUnit.test('isWeakMap', function(assert) {
+    assert.ok(!_.isWeakMap('string'), 'a string is not a weakmap');
+    assert.ok(!_.isWeakMap(2), 'a number is not a weakmap');
+    assert.ok(!_.isWeakMap({}), 'an object is not a weakmap');
+    assert.ok(!_.isWeakMap(false), 'a boolean is not a weakmap');
+    assert.ok(!_.isWeakMap(void 0), 'undefined is not a weakmap');
+    assert.ok(!_.isWeakMap([1, 2, 3]), 'an array is not a weakmap');
+    if (typeof Set === 'function') {
+      assert.ok(!_.isWeakMap(new Set()), 'a set is not a weakmap');
+    }
+    if (typeof WeakSet === 'function') {
+      assert.ok(!_.isWeakMap(new WeakSet()), 'a weakset is not a weakmap');
+    }
+    if (typeof Map === 'function') {
+      assert.ok(!_.isWeakMap(new Map()), 'a map is not a weakmap');
+    }
+    if (typeof WeakMap === 'function') {
+      var keyObj = {}, obj = new WeakMap();
+      obj.set(keyObj, 'value');
+      assert.ok(_.isWeakMap(obj), 'but a weakmap is');
+    }
+  });
+
+  QUnit.test('isSet', function(assert) {
+    assert.ok(!_.isSet('string'), 'a string is not a set');
+    assert.ok(!_.isSet(2), 'a number is not a set');
+    assert.ok(!_.isSet({}), 'an object is not a set');
+    assert.ok(!_.isSet(false), 'a boolean is not a set');
+    assert.ok(!_.isSet(void 0), 'undefined is not a set');
+    assert.ok(!_.isSet([1, 2, 3]), 'an array is not a set');
+    if (typeof Map === 'function') {
+      assert.ok(!_.isSet(new Map()), 'a map is not a set');
+    }
+    if (typeof WeakMap === 'function') {
+      assert.ok(!_.isSet(new WeakMap()), 'a weakmap is not a set');
+    }
+    if (typeof WeakSet === 'function') {
+      assert.ok(!_.isSet(new WeakSet()), 'a weakset is not a set');
+    }
+    if (typeof Set === 'function') {
+      var obj = new Set();
+      obj.add(1).add('string').add(false).add({});
+      assert.ok(_.isSet(obj), 'but a set is');
+    }
+  });
+
+  QUnit.test('isWeakSet', function(assert) {
+
+    assert.ok(!_.isWeakSet('string'), 'a string is not a weakset');
+    assert.ok(!_.isWeakSet(2), 'a number is not a weakset');
+    assert.ok(!_.isWeakSet({}), 'an object is not a weakset');
+    assert.ok(!_.isWeakSet(false), 'a boolean is not a weakset');
+    assert.ok(!_.isWeakSet(void 0), 'undefined is not a weakset');
+    assert.ok(!_.isWeakSet([1, 2, 3]), 'an array is not a weakset');
+    if (typeof Map === 'function') {
+      assert.ok(!_.isWeakSet(new Map()), 'a map is not a weakset');
+    }
+    if (typeof WeakMap === 'function') {
+      assert.ok(!_.isWeakSet(new WeakMap()), 'a weakmap is not a weakset');
+    }
+    if (typeof Set === 'function') {
+      assert.ok(!_.isWeakSet(new Set()), 'a set is not a weakset');
+    }
+    if (typeof WeakSet === 'function') {
+      var obj = new WeakSet();
+      obj.add({x: 1}, {y: 'string'}).add({y: 'string'}).add({z: [1, 2, 3]});
+      assert.ok(_.isWeakSet(obj), 'but a weakset is');
+    }
+  });
+
+  QUnit.test('isFunction', function(assert) {
+    assert.ok(!_.isFunction(void 0), 'undefined vars are not functions');
+    assert.ok(!_.isFunction([1, 2, 3]), 'arrays are not functions');
+    assert.ok(!_.isFunction('moe'), 'strings are not functions');
+    assert.ok(_.isFunction(_.isFunction), 'but functions are');
+    assert.ok(_.isFunction(function(){}), 'even anonymous ones');
+
+    if (testElement) {
+      assert.ok(!_.isFunction(testElement), 'elements are not functions');
+    }
+
+    var nodelist = typeof document != 'undefined' && document.childNodes;
+    if (nodelist) {
+      assert.ok(!_.isFunction(nodelist));
+    }
+  });
+
+  if (typeof Int8Array !== 'undefined') {
+    QUnit.test('#1929 Typed Array constructors are functions', function(assert) {
+      _.chain(['Float32Array', 'Float64Array', 'Int8Array', 'Int16Array', 'Int32Array', 'Uint8Array', 'Uint8ClampedArray', 'Uint16Array', 'Uint32Array'])
+      .map(_.propertyOf(typeof GLOBAL != 'undefined' ? GLOBAL : window))
+      .compact()
+      .each(function(TypedArray) {
+        // PhantomJS reports `typeof UInt8Array == 'object'` and doesn't report toString TypeArray
+        // as a function
+        assert.strictEqual(_.isFunction(TypedArray), Object.prototype.toString.call(TypedArray) === '[object Function]');
+      });
+    });
+  }
+
+  QUnit.test('isDate', function(assert) {
+    assert.ok(!_.isDate(100), 'numbers are not dates');
+    assert.ok(!_.isDate({}), 'objects are not dates');
+    assert.ok(_.isDate(new Date()), 'but dates are');
+  });
+
+  QUnit.test('isRegExp', function(assert) {
+    assert.ok(!_.isRegExp(_.identity), 'functions are not RegExps');
+    assert.ok(_.isRegExp(/identity/), 'but RegExps are');
+  });
+
+  QUnit.test('isFinite', function(assert) {
+    assert.ok(!_.isFinite(void 0), 'undefined is not finite');
+    assert.ok(!_.isFinite(null), 'null is not finite');
+    assert.ok(!_.isFinite(NaN), 'NaN is not finite');
+    assert.ok(!_.isFinite(Infinity), 'Infinity is not finite');
+    assert.ok(!_.isFinite(-Infinity), '-Infinity is not finite');
+    assert.ok(_.isFinite('12'), 'Numeric strings are numbers');
+    assert.ok(!_.isFinite('1a'), 'Non numeric strings are not numbers');
+    assert.ok(!_.isFinite(''), 'Empty strings are not numbers');
+    var obj = new Number(5);
+    assert.ok(_.isFinite(obj), 'Number instances can be finite');
+    assert.ok(_.isFinite(0), '0 is finite');
+    assert.ok(_.isFinite(123), 'Ints are finite');
+    assert.ok(_.isFinite(-12.44), 'Floats are finite');
+    if (typeof Symbol === 'function') {
+      assert.ok(!_.isFinite(Symbol()), 'symbols are not numbers');
+      assert.ok(!_.isFinite(Symbol('description')), 'described symbols are not numbers');
+      assert.ok(!_.isFinite(Object(Symbol())), 'boxed symbols are not numbers');
+    }
+  });
+
+  QUnit.test('isNaN', function(assert) {
+    assert.ok(!_.isNaN(void 0), 'undefined is not NaN');
+    assert.ok(!_.isNaN(null), 'null is not NaN');
+    assert.ok(!_.isNaN(0), '0 is not NaN');
+    assert.ok(!_.isNaN(new Number(0)), 'wrapped 0 is not NaN');
+    assert.ok(_.isNaN(NaN), 'but NaN is');
+    assert.ok(_.isNaN(new Number(NaN)), 'wrapped NaN is still NaN');
+  });
+
+  QUnit.test('isNull', function(assert) {
+    assert.ok(!_.isNull(void 0), 'undefined is not null');
+    assert.ok(!_.isNull(NaN), 'NaN is not null');
+    assert.ok(_.isNull(null), 'but null is');
+  });
+
+  QUnit.test('isUndefined', function(assert) {
+    assert.ok(!_.isUndefined(1), 'numbers are defined');
+    assert.ok(!_.isUndefined(null), 'null is defined');
+    assert.ok(!_.isUndefined(false), 'false is defined');
+    assert.ok(!_.isUndefined(NaN), 'NaN is defined');
+    assert.ok(_.isUndefined(), 'nothing is undefined');
+    assert.ok(_.isUndefined(void 0), 'undefined is undefined');
+  });
+
+  QUnit.test('isError', function(assert) {
+    assert.ok(!_.isError(1), 'numbers are not Errors');
+    assert.ok(!_.isError(null), 'null is not an Error');
+    assert.ok(!_.isError(Error), 'functions are not Errors');
+    assert.ok(_.isError(new Error()), 'Errors are Errors');
+    assert.ok(_.isError(new EvalError()), 'EvalErrors are Errors');
+    assert.ok(_.isError(new RangeError()), 'RangeErrors are Errors');
+    assert.ok(_.isError(new ReferenceError()), 'ReferenceErrors are Errors');
+    assert.ok(_.isError(new SyntaxError()), 'SyntaxErrors are Errors');
+    assert.ok(_.isError(new TypeError()), 'TypeErrors are Errors');
+    assert.ok(_.isError(new URIError()), 'URIErrors are Errors');
+  });
+
+  QUnit.test('tap', function(assert) {
+    var intercepted = null;
+    var interceptor = function(obj) { intercepted = obj; };
+    var returned = _.tap(1, interceptor);
+    assert.equal(intercepted, 1, 'passes tapped object to interceptor');
+    assert.equal(returned, 1, 'returns tapped object');
+
+    returned = _([1, 2, 3]).chain().
+      map(function(n){ return n * 2; }).
+      max().
+      tap(interceptor).
+      value();
+    assert.equal(returned, 6, 'can use tapped objects in a chain');
+    assert.equal(intercepted, returned, 'can use tapped objects in a chain');
+  });
+
+  QUnit.test('has', function(assert) {
+    var obj = {foo: 'bar', func: function(){}};
+    assert.ok(_.has(obj, 'foo'), 'has() checks that the object has a property.');
+    assert.ok(!_.has(obj, 'baz'), "has() returns false if the object doesn't have the property.");
+    assert.ok(_.has(obj, 'func'), 'has() works for functions too.');
+    obj.hasOwnProperty = null;
+    assert.ok(_.has(obj, 'foo'), 'has() works even when the hasOwnProperty method is deleted.');
+    var child = {};
+    child.prototype = obj;
+    assert.ok(!_.has(child, 'foo'), 'has() does not check the prototype chain for a property.');
+    assert.strictEqual(_.has(null, 'foo'), false, 'has() returns false for null');
+    assert.strictEqual(_.has(void 0, 'foo'), false, 'has() returns false for undefined');
+  });
+
+  QUnit.test('isMatch', function(assert) {
+    var moe = {name: 'Moe Howard', hair: true};
+    var curly = {name: 'Curly Howard', hair: false};
+
+    assert.equal(_.isMatch(moe, {hair: true}), true, 'Returns a boolean');
+    assert.equal(_.isMatch(curly, {hair: true}), false, 'Returns a boolean');
+
+    assert.equal(_.isMatch(5, {__x__: void 0}), false, 'can match undefined props on primitives');
+    assert.equal(_.isMatch({__x__: void 0}, {__x__: void 0}), true, 'can match undefined props');
+
+    assert.equal(_.isMatch(null, {}), true, 'Empty spec called with null object returns true');
+    assert.equal(_.isMatch(null, {a: 1}), false, 'Non-empty spec called with null object returns false');
+
+    _.each([null, void 0], function(item) { assert.strictEqual(_.isMatch(item, null), true, 'null matches null'); });
+    _.each([null, void 0], function(item) { assert.strictEqual(_.isMatch(item, null), true, 'null matches {}'); });
+    assert.strictEqual(_.isMatch({b: 1}, {a: void 0}), false, 'handles undefined values (1683)');
+
+    _.each([true, 5, NaN, null, void 0], function(item) {
+      assert.strictEqual(_.isMatch({a: 1}, item), true, 'treats primitives as empty');
+    });
+
+    function Prototest() {}
+    Prototest.prototype.x = 1;
+    var specObj = new Prototest;
+    assert.equal(_.isMatch({x: 2}, specObj), true, 'spec is restricted to own properties');
+
+    specObj.y = 5;
+    assert.equal(_.isMatch({x: 1, y: 5}, specObj), true);
+    assert.equal(_.isMatch({x: 1, y: 4}, specObj), false);
+
+    assert.ok(_.isMatch(specObj, {x: 1, y: 5}), 'inherited and own properties are checked on the test object');
+
+    Prototest.x = 5;
+    assert.ok(_.isMatch({x: 5, y: 1}, Prototest), 'spec can be a function');
+
+    //null edge cases
+    var oCon = {constructor: Object};
+    assert.deepEqual(_.map([null, void 0, 5, {}], _.partial(_.isMatch, _, oCon)), [false, false, false, true], 'doesnt falsey match constructor on undefined/null');
+  });
+
+  QUnit.test('matcher', function(assert) {
+    var moe = {name: 'Moe Howard', hair: true};
+    var curly = {name: 'Curly Howard', hair: false};
+    var stooges = [moe, curly];
+
+    assert.equal(_.matcher({hair: true})(moe), true, 'Returns a boolean');
+    assert.equal(_.matcher({hair: true})(curly), false, 'Returns a boolean');
+
+    assert.equal(_.matcher({__x__: void 0})(5), false, 'can match undefined props on primitives');
+    assert.equal(_.matcher({__x__: void 0})({__x__: void 0}), true, 'can match undefined props');
+
+    assert.equal(_.matcher({})(null), true, 'Empty spec called with null object returns true');
+    assert.equal(_.matcher({a: 1})(null), false, 'Non-empty spec called with null object returns false');
+
+    assert.ok(_.find(stooges, _.matcher({hair: false})) === curly, 'returns a predicate that can be used by finding functions.');
+    assert.ok(_.find(stooges, _.matcher(moe)) === moe, 'can be used to locate an object exists in a collection.');
+    assert.deepEqual(_.filter([null, void 0], _.matcher({a: 1})), [], 'Do not throw on null values.');
+
+    assert.deepEqual(_.filter([null, void 0], _.matcher(null)), [null, void 0], 'null matches null');
+    assert.deepEqual(_.filter([null, void 0], _.matcher({})), [null, void 0], 'null matches {}');
+    assert.deepEqual(_.filter([{b: 1}], _.matcher({a: void 0})), [], 'handles undefined values (1683)');
+
+    _.each([true, 5, NaN, null, void 0], function(item) {
+      assert.equal(_.matcher(item)({a: 1}), true, 'treats primitives as empty');
+    });
+
+    function Prototest() {}
+    Prototest.prototype.x = 1;
+    var specObj = new Prototest;
+    var protospec = _.matcher(specObj);
+    assert.equal(protospec({x: 2}), true, 'spec is restricted to own properties');
+
+    specObj.y = 5;
+    protospec = _.matcher(specObj);
+    assert.equal(protospec({x: 1, y: 5}), true);
+    assert.equal(protospec({x: 1, y: 4}), false);
+
+    assert.ok(_.matcher({x: 1, y: 5})(specObj), 'inherited and own properties are checked on the test object');
+
+    Prototest.x = 5;
+    assert.ok(_.matcher(Prototest)({x: 5, y: 1}), 'spec can be a function');
+
+    // #1729
+    var o = {b: 1};
+    var m = _.matcher(o);
+
+    assert.equal(m({b: 1}), true);
+    o.b = 2;
+    o.a = 1;
+    assert.equal(m({b: 1}), true, 'changing spec object doesnt change matches result');
+
+
+    //null edge cases
+    var oCon = _.matcher({constructor: Object});
+    assert.deepEqual(_.map([null, void 0, 5, {}], oCon), [false, false, false, true], 'doesnt falsey match constructor on undefined/null');
+  });
+
+  QUnit.test('matches', function(assert) {
+    assert.strictEqual(_.matches, _.matcher, 'is an alias for matcher');
+  });
+
+  QUnit.test('findKey', function(assert) {
+    var objects = {
+      a: {a: 0, b: 0},
+      b: {a: 1, b: 1},
+      c: {a: 2, b: 2}
+    };
+
+    assert.equal(_.findKey(objects, function(obj) {
+      return obj.a === 0;
+    }), 'a');
+
+    assert.equal(_.findKey(objects, function(obj) {
+      return obj.b * obj.a === 4;
+    }), 'c');
+
+    assert.equal(_.findKey(objects, 'a'), 'b', 'Uses lookupIterator');
+
+    assert.equal(_.findKey(objects, function(obj) {
+      return obj.b * obj.a === 5;
+    }), void 0);
+
+    assert.strictEqual(_.findKey([1, 2, 3, 4, 5, 6], function(obj) {
+      return obj === 3;
+    }), '2', 'Keys are strings');
+
+    assert.strictEqual(_.findKey(objects, function(a) {
+      return a.foo === null;
+    }), void 0);
+
+    _.findKey({a: {a: 1}}, function(a, key, obj) {
+      assert.equal(key, 'a');
+      assert.deepEqual(obj, {a: {a: 1}});
+      assert.strictEqual(this, objects, 'called with context');
+    }, objects);
+
+    var array = [1, 2, 3, 4];
+    array.match = 55;
+    assert.strictEqual(_.findKey(array, function(x) { return x === 55; }), 'match', 'matches array-likes keys');
+  });
+
+
+  QUnit.test('mapObject', function(assert) {
+    var obj = {a: 1, b: 2};
+    var objects = {
+      a: {a: 0, b: 0},
+      b: {a: 1, b: 1},
+      c: {a: 2, b: 2}
+    };
+
+    assert.deepEqual(_.mapObject(obj, function(val) {
+      return val * 2;
+    }), {a: 2, b: 4}, 'simple objects');
+
+    assert.deepEqual(_.mapObject(objects, function(val) {
+      return _.reduce(val, function(memo, v){
+        return memo + v;
+      }, 0);
+    }), {a: 0, b: 2, c: 4}, 'nested objects');
+
+    assert.deepEqual(_.mapObject(obj, function(val, key, o) {
+      return o[key] * 2;
+    }), {a: 2, b: 4}, 'correct keys');
+
+    assert.deepEqual(_.mapObject([1, 2], function(val) {
+      return val * 2;
+    }), {0: 2, 1: 4}, 'check behavior for arrays');
+
+    assert.deepEqual(_.mapObject(obj, function(val) {
+      return val * this.multiplier;
+    }, {multiplier: 3}), {a: 3, b: 6}, 'keep context');
+
+    assert.deepEqual(_.mapObject({a: 1}, function() {
+      return this.length;
+    }, [1, 2]), {a: 2}, 'called with context');
+
+    var ids = _.mapObject({length: 2, 0: {id: '1'}, 1: {id: '2'}}, function(n){
+      return n.id;
+    });
+    assert.deepEqual(ids, {length: void 0, 0: '1', 1: '2'}, 'Check with array-like objects');
+
+    // Passing a property name like _.pluck.
+    var people = {a: {name: 'moe', age: 30}, b: {name: 'curly', age: 50}};
+    assert.deepEqual(_.mapObject(people, 'name'), {a: 'moe', b: 'curly'}, 'predicate string map to object properties');
+
+    _.each([null, void 0, 1, 'abc', [], {}, void 0], function(val){
+      assert.deepEqual(_.mapObject(val, _.identity), {}, 'mapValue identity');
+    });
+
+    var Proto = function(){ this.a = 1; };
+    Proto.prototype.b = 1;
+    var protoObj = new Proto();
+    assert.deepEqual(_.mapObject(protoObj, _.identity), {a: 1}, 'ignore inherited values from prototypes');
+
+  });
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/utility.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/utility.js
new file mode 100644
index 0000000..fbd54df
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/test/utility.js
@@ -0,0 +1,420 @@
+(function() {
+  var _ = typeof require == 'function' ? require('..') : window._;
+  var templateSettings;
+
+  QUnit.module('Utility', {
+
+    beforeEach: function() {
+      templateSettings = _.clone(_.templateSettings);
+    },
+
+    afterEach: function() {
+      _.templateSettings = templateSettings;
+    }
+
+  });
+
+  if (typeof this == 'object') {
+    QUnit.test('noConflict', function(assert) {
+      var underscore = _.noConflict();
+      assert.equal(underscore.identity(1), 1);
+      if (typeof require != 'function') {
+        assert.equal(this._, void 0, 'global underscore is removed');
+        this._ = underscore;
+      } else if (typeof global !== 'undefined') {
+        delete global._;
+      }
+    });
+  }
+
+  if (typeof require == 'function') {
+    QUnit.test('noConflict (node vm)', function(assert) {
+      assert.expect(2);
+      var done = assert.async();
+      var fs = require('fs');
+      var vm = require('vm');
+      var filename = __dirname + '/../underscore.js';
+      fs.readFile(filename, function(err, content){
+        var sandbox = vm.createScript(
+          content + 'this.underscore = this._.noConflict();',
+          filename
+        );
+        var context = {_: 'oldvalue'};
+        sandbox.runInNewContext(context);
+        assert.equal(context._, 'oldvalue');
+        assert.equal(context.underscore.VERSION, _.VERSION);
+
+        done();
+      });
+    });
+  }
+
+  QUnit.test('#750 - Return _ instance.', function(assert) {
+    assert.expect(2);
+    var instance = _([]);
+    assert.ok(_(instance) === instance);
+    assert.ok(new _(instance) === instance);
+  });
+
+  QUnit.test('identity', function(assert) {
+    var stooge = {name: 'moe'};
+    assert.equal(_.identity(stooge), stooge, 'stooge is the same as his identity');
+  });
+
+  QUnit.test('constant', function(assert) {
+    var stooge = {name: 'moe'};
+    assert.equal(_.constant(stooge)(), stooge, 'should create a function that returns stooge');
+  });
+
+  QUnit.test('noop', function(assert) {
+    assert.strictEqual(_.noop('curly', 'larry', 'moe'), void 0, 'should always return undefined');
+  });
+
+  QUnit.test('property', function(assert) {
+    var stooge = {name: 'moe'};
+    assert.equal(_.property('name')(stooge), 'moe', 'should return the property with the given name');
+    assert.equal(_.property('name')(null), void 0, 'should return undefined for null values');
+    assert.equal(_.property('name')(void 0), void 0, 'should return undefined for undefined values');
+  });
+
+  QUnit.test('propertyOf', function(assert) {
+    var stoogeRanks = _.propertyOf({curly: 2, moe: 1, larry: 3});
+    assert.equal(stoogeRanks('curly'), 2, 'should return the property with the given name');
+    assert.equal(stoogeRanks(null), void 0, 'should return undefined for null values');
+    assert.equal(stoogeRanks(void 0), void 0, 'should return undefined for undefined values');
+
+    function MoreStooges() { this.shemp = 87; }
+    MoreStooges.prototype = {curly: 2, moe: 1, larry: 3};
+    var moreStoogeRanks = _.propertyOf(new MoreStooges());
+    assert.equal(moreStoogeRanks('curly'), 2, 'should return properties from further up the prototype chain');
+
+    var nullPropertyOf = _.propertyOf(null);
+    assert.equal(nullPropertyOf('curly'), void 0, 'should return undefined when obj is null');
+
+    var undefPropertyOf = _.propertyOf(void 0);
+    assert.equal(undefPropertyOf('curly'), void 0, 'should return undefined when obj is undefined');
+  });
+
+  QUnit.test('random', function(assert) {
+    var array = _.range(1000);
+    var min = Math.pow(2, 31);
+    var max = Math.pow(2, 62);
+
+    assert.ok(_.every(array, function() {
+      return _.random(min, max) >= min;
+    }), 'should produce a random number greater than or equal to the minimum number');
+
+    assert.ok(_.some(array, function() {
+      return _.random(Number.MAX_VALUE) > 0;
+    }), 'should produce a random number when passed `Number.MAX_VALUE`');
+  });
+
+  QUnit.test('now', function(assert) {
+    var diff = _.now() - new Date().getTime();
+    assert.ok(diff <= 0 && diff > -5, 'Produces the correct time in milliseconds');//within 5ms
+  });
+
+  QUnit.test('uniqueId', function(assert) {
+    var ids = [], i = 0;
+    while (i++ < 100) ids.push(_.uniqueId());
+    assert.equal(_.uniq(ids).length, ids.length, 'can generate a globally-unique stream of ids');
+  });
+
+  QUnit.test('times', function(assert) {
+    var vals = [];
+    _.times(3, function(i) { vals.push(i); });
+    assert.deepEqual(vals, [0, 1, 2], 'is 0 indexed');
+    //
+    vals = [];
+    _(3).times(function(i) { vals.push(i); });
+    assert.deepEqual(vals, [0, 1, 2], 'works as a wrapper');
+    // collects return values
+    assert.deepEqual([0, 1, 2], _.times(3, function(i) { return i; }), 'collects return values');
+
+    assert.deepEqual(_.times(0, _.identity), []);
+    assert.deepEqual(_.times(-1, _.identity), []);
+    assert.deepEqual(_.times(parseFloat('-Infinity'), _.identity), []);
+  });
+
+  QUnit.test('mixin', function(assert) {
+    _.mixin({
+      myReverse: function(string) {
+        return string.split('').reverse().join('');
+      }
+    });
+    assert.equal(_.myReverse('panacea'), 'aecanap', 'mixed in a function to _');
+    assert.equal(_('champ').myReverse(), 'pmahc', 'mixed in a function to the OOP wrapper');
+  });
+
+  QUnit.test('_.escape', function(assert) {
+    assert.equal(_.escape(null), '');
+  });
+
+  QUnit.test('_.unescape', function(assert) {
+    var string = 'Curly & Moe';
+    assert.equal(_.unescape(null), '');
+    assert.equal(_.unescape(_.escape(string)), string);
+    assert.equal(_.unescape(string), string, 'don\'t unescape unnecessarily');
+  });
+
+  // Don't care what they escape them to just that they're escaped and can be unescaped
+  QUnit.test('_.escape & unescape', function(assert) {
+    // test & (&amp;) seperately obviously
+    var escapeCharacters = ['<', '>', '"', '\'', '`'];
+
+    _.each(escapeCharacters, function(escapeChar) {
+      var s = 'a ' + escapeChar + ' string escaped';
+      var e = _.escape(s);
+      assert.notEqual(s, e, escapeChar + ' is escaped');
+      assert.equal(s, _.unescape(e), escapeChar + ' can be unescaped');
+
+      s = 'a ' + escapeChar + escapeChar + escapeChar + 'some more string' + escapeChar;
+      e = _.escape(s);
+
+      assert.equal(e.indexOf(escapeChar), -1, 'can escape multiple occurances of ' + escapeChar);
+      assert.equal(_.unescape(e), s, 'multiple occurrences of ' + escapeChar + ' can be unescaped');
+    });
+
+    // handles multiple escape characters at once
+    var joiner = ' other stuff ';
+    var allEscaped = escapeCharacters.join(joiner);
+    allEscaped += allEscaped;
+    assert.ok(_.every(escapeCharacters, function(escapeChar) {
+      return allEscaped.indexOf(escapeChar) !== -1;
+    }), 'handles multiple characters');
+    assert.ok(allEscaped.indexOf(joiner) >= 0, 'can escape multiple escape characters at the same time');
+
+    // test & -> &amp;
+    var str = 'some string & another string & yet another';
+    var escaped = _.escape(str);
+
+    assert.ok(escaped.indexOf('&') !== -1, 'handles & aka &amp;');
+    assert.equal(_.unescape(str), str, 'can unescape &amp;');
+  });
+
+  QUnit.test('template', function(assert) {
+    var basicTemplate = _.template("<%= thing %> is gettin' on my noives!");
+    var result = basicTemplate({thing: 'This'});
+    assert.equal(result, "This is gettin' on my noives!", 'can do basic attribute interpolation');
+
+    var sansSemicolonTemplate = _.template('A <% this %> B');
+    assert.equal(sansSemicolonTemplate(), 'A  B');
+
+    var backslashTemplate = _.template('<%= thing %> is \\ridanculous');
+    assert.equal(backslashTemplate({thing: 'This'}), 'This is \\ridanculous');
+
+    var escapeTemplate = _.template('<%= a ? "checked=\\"checked\\"" : "" %>');
+    assert.equal(escapeTemplate({a: true}), 'checked="checked"', 'can handle slash escapes in interpolations.');
+
+    var fancyTemplate = _.template('<ul><% ' +
+    '  for (var key in people) { ' +
+    '%><li><%= people[key] %></li><% } %></ul>');
+    result = fancyTemplate({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}});
+    assert.equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
+
+    var escapedCharsInJavascriptTemplate = _.template('<ul><% _.each(numbers.split("\\n"), function(item) { %><li><%= item %></li><% }) %></ul>');
+    result = escapedCharsInJavascriptTemplate({numbers: 'one\ntwo\nthree\nfour'});
+    assert.equal(result, '<ul><li>one</li><li>two</li><li>three</li><li>four</li></ul>', 'Can use escaped characters (e.g. \\n) in JavaScript');
+
+    var namespaceCollisionTemplate = _.template('<%= pageCount %> <%= thumbnails[pageCount] %> <% _.each(thumbnails, function(p) { %><div class="thumbnail" rel="<%= p %>"></div><% }); %>');
+    result = namespaceCollisionTemplate({
+      pageCount: 3,
+      thumbnails: {
+        1: 'p1-thumbnail.gif',
+        2: 'p2-thumbnail.gif',
+        3: 'p3-thumbnail.gif'
+      }
+    });
+    assert.equal(result, '3 p3-thumbnail.gif <div class="thumbnail" rel="p1-thumbnail.gif"></div><div class="thumbnail" rel="p2-thumbnail.gif"></div><div class="thumbnail" rel="p3-thumbnail.gif"></div>');
+
+    var noInterpolateTemplate = _.template('<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>');
+    result = noInterpolateTemplate();
+    assert.equal(result, '<div><p>Just some text. Hey, I know this is silly but it aids consistency.</p></div>');
+
+    var quoteTemplate = _.template("It's its, not it's");
+    assert.equal(quoteTemplate({}), "It's its, not it's");
+
+    var quoteInStatementAndBody = _.template('<% ' +
+    "  if(foo == 'bar'){ " +
+    "%>Statement quotes and 'quotes'.<% } %>");
+    assert.equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
+
+    var withNewlinesAndTabs = _.template('This\n\t\tis: <%= x %>.\n\tok.\nend.');
+    assert.equal(withNewlinesAndTabs({x: 'that'}), 'This\n\t\tis: that.\n\tok.\nend.');
+
+    var template = _.template('<i><%- value %></i>');
+    result = template({value: '<script>'});
+    assert.equal(result, '<i>&lt;script&gt;</i>');
+
+    var stooge = {
+      name: 'Moe',
+      template: _.template("I'm <%= this.name %>")
+    };
+    assert.equal(stooge.template(), "I'm Moe");
+
+    template = _.template('\n ' +
+    '  <%\n ' +
+    '  // a comment\n ' +
+    '  if (data) { data += 12345; }; %>\n ' +
+    '  <li><%= data %></li>\n '
+    );
+    assert.equal(template({data: 12345}).replace(/\s/g, ''), '<li>24690</li>');
+
+    _.templateSettings = {
+      evaluate: /\{\{([\s\S]+?)\}\}/g,
+      interpolate: /\{\{=([\s\S]+?)\}\}/g
+    };
+
+    var custom = _.template('<ul>{{ for (var key in people) { }}<li>{{= people[key] }}</li>{{ } }}</ul>');
+    result = custom({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}});
+    assert.equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
+
+    var customQuote = _.template("It's its, not it's");
+    assert.equal(customQuote({}), "It's its, not it's");
+
+    quoteInStatementAndBody = _.template("{{ if(foo == 'bar'){ }}Statement quotes and 'quotes'.{{ } }}");
+    assert.equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
+
+    _.templateSettings = {
+      evaluate: /<\?([\s\S]+?)\?>/g,
+      interpolate: /<\?=([\s\S]+?)\?>/g
+    };
+
+    var customWithSpecialChars = _.template('<ul><? for (var key in people) { ?><li><?= people[key] ?></li><? } ?></ul>');
+    result = customWithSpecialChars({people: {moe: 'Moe', larry: 'Larry', curly: 'Curly'}});
+    assert.equal(result, '<ul><li>Moe</li><li>Larry</li><li>Curly</li></ul>', 'can run arbitrary javascript in templates');
+
+    var customWithSpecialCharsQuote = _.template("It's its, not it's");
+    assert.equal(customWithSpecialCharsQuote({}), "It's its, not it's");
+
+    quoteInStatementAndBody = _.template("<? if(foo == 'bar'){ ?>Statement quotes and 'quotes'.<? } ?>");
+    assert.equal(quoteInStatementAndBody({foo: 'bar'}), "Statement quotes and 'quotes'.");
+
+    _.templateSettings = {
+      interpolate: /\{\{(.+?)\}\}/g
+    };
+
+    var mustache = _.template('Hello {{planet}}!');
+    assert.equal(mustache({planet: 'World'}), 'Hello World!', 'can mimic mustache.js');
+
+    var templateWithNull = _.template('a null undefined {{planet}}');
+    assert.equal(templateWithNull({planet: 'world'}), 'a null undefined world', 'can handle missing escape and evaluate settings');
+  });
+
+  QUnit.test('_.template provides the generated function source, when a SyntaxError occurs', function(assert) {
+    var source;
+    try {
+      _.template('<b><%= if x %></b>');
+    } catch (ex) {
+      source = ex.source;
+    }
+    assert.ok(/__p/.test(source));
+  });
+
+  QUnit.test('_.template handles \\u2028 & \\u2029', function(assert) {
+    var tmpl = _.template('<p>\u2028<%= "\\u2028\\u2029" %>\u2029</p>');
+    assert.strictEqual(tmpl(), '<p>\u2028\u2028\u2029\u2029</p>');
+  });
+
+  QUnit.test('result calls functions and returns primitives', function(assert) {
+    var obj = {w: '', x: 'x', y: function(){ return this.x; }};
+    assert.strictEqual(_.result(obj, 'w'), '');
+    assert.strictEqual(_.result(obj, 'x'), 'x');
+    assert.strictEqual(_.result(obj, 'y'), 'x');
+    assert.strictEqual(_.result(obj, 'z'), void 0);
+    assert.strictEqual(_.result(null, 'x'), void 0);
+  });
+
+  QUnit.test('result returns a default value if object is null or undefined', function(assert) {
+    assert.strictEqual(_.result(null, 'b', 'default'), 'default');
+    assert.strictEqual(_.result(void 0, 'c', 'default'), 'default');
+    assert.strictEqual(_.result(''.match('missing'), 1, 'default'), 'default');
+  });
+
+  QUnit.test('result returns a default value if property of object is missing', function(assert) {
+    assert.strictEqual(_.result({d: null}, 'd', 'default'), null);
+    assert.strictEqual(_.result({e: false}, 'e', 'default'), false);
+  });
+
+  QUnit.test('result only returns the default value if the object does not have the property or is undefined', function(assert) {
+    assert.strictEqual(_.result({}, 'b', 'default'), 'default');
+    assert.strictEqual(_.result({d: void 0}, 'd', 'default'), 'default');
+  });
+
+  QUnit.test('result does not return the default if the property of an object is found in the prototype', function(assert) {
+    var Foo = function(){};
+    Foo.prototype.bar = 1;
+    assert.strictEqual(_.result(new Foo, 'bar', 2), 1);
+  });
+
+  QUnit.test('result does use the fallback when the result of invoking the property is undefined', function(assert) {
+    var obj = {a: function() {}};
+    assert.strictEqual(_.result(obj, 'a', 'failed'), void 0);
+  });
+
+  QUnit.test('result fallback can use a function', function(assert) {
+    var obj = {a: [1, 2, 3]};
+    assert.strictEqual(_.result(obj, 'b', _.constant(5)), 5);
+    assert.strictEqual(_.result(obj, 'b', function() {
+      return this.a;
+    }), obj.a, 'called with context');
+  });
+
+  QUnit.test('_.templateSettings.variable', function(assert) {
+    var s = '<%=data.x%>';
+    var data = {x: 'x'};
+    var tmp = _.template(s, {variable: 'data'});
+    assert.strictEqual(tmp(data), 'x');
+    _.templateSettings.variable = 'data';
+    assert.strictEqual(_.template(s)(data), 'x');
+  });
+
+  QUnit.test('#547 - _.templateSettings is unchanged by custom settings.', function(assert) {
+    assert.ok(!_.templateSettings.variable);
+    _.template('', {}, {variable: 'x'});
+    assert.ok(!_.templateSettings.variable);
+  });
+
+  QUnit.test('#556 - undefined template variables.', function(assert) {
+    var template = _.template('<%=x%>');
+    assert.strictEqual(template({x: null}), '');
+    assert.strictEqual(template({x: void 0}), '');
+
+    var templateEscaped = _.template('<%-x%>');
+    assert.strictEqual(templateEscaped({x: null}), '');
+    assert.strictEqual(templateEscaped({x: void 0}), '');
+
+    var templateWithProperty = _.template('<%=x.foo%>');
+    assert.strictEqual(templateWithProperty({x: {}}), '');
+    assert.strictEqual(templateWithProperty({x: {}}), '');
+
+    var templateWithPropertyEscaped = _.template('<%-x.foo%>');
+    assert.strictEqual(templateWithPropertyEscaped({x: {}}), '');
+    assert.strictEqual(templateWithPropertyEscaped({x: {}}), '');
+  });
+
+  QUnit.test('interpolate evaluates code only once.', function(assert) {
+    assert.expect(2);
+    var count = 0;
+    var template = _.template('<%= f() %>');
+    template({f: function(){ assert.ok(!count++); }});
+
+    var countEscaped = 0;
+    var templateEscaped = _.template('<%- f() %>');
+    templateEscaped({f: function(){ assert.ok(!countEscaped++); }});
+  });
+
+  QUnit.test('#746 - _.template settings are not modified.', function(assert) {
+    assert.expect(1);
+    var settings = {};
+    _.template('', null, settings);
+    assert.deepEqual(settings, {});
+  });
+
+  QUnit.test('#779 - delimeters are applied to unescaped text.', function(assert) {
+    assert.expect(1);
+    var template = _.template('<<\nx\n>>', null, {evaluate: /<<(.*?)>>/g});
+    assert.strictEqual(template(), '<<\nx\n>>');
+  });
+
+}());
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/underscore-min.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/underscore-min.js
new file mode 100644
index 0000000..f01025b
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/underscore-min.js
@@ -0,0 +1,6 @@
+//     Underscore.js 1.8.3
+//     http://underscorejs.org
+//     (c) 2009-2015 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+(function(){function n(n){function t(t,r,e,u,i,o){for(;i>=0&&o>i;i+=n){var a=u?u[i]:i;e=r(e,t[a],a,t)}return e}return function(r,e,u,i){e=b(e,i,4);var o=!k(r)&&m.keys(r),a=(o||r).length,c=n>0?0:a-1;return arguments.length<3&&(u=r[o?o[c]:c],c+=n),t(r,e,u,o,c,a)}}function t(n){return function(t,r,e){r=x(r,e);for(var u=O(t),i=n>0?0:u-1;i>=0&&u>i;i+=n)if(r(t[i],i,t))return i;return-1}}function r(n,t,r){return function(e,u,i){var o=0,a=O(e);if("number"==typeof i)n>0?o=i>=0?i:Math.max(i+a,o):a=i>=0?Math.min(i+1,a):i+a+1;else if(r&&i&&a)return i=r(e,u),e[i]===u?i:-1;if(u!==u)return i=t(l.call(e,o,a),m.isNaN),i>=0?i+o:-1;for(i=n>0?o:a-1;i>=0&&a>i;i+=n)if(e[i]===u)return i;return-1}}function e(n,t){var r=I.length,e=n.constructor,u=m.isFunction(e)&&e.prototype||a,i="constructor";for(m.has(n,i)&&!m.contains(t,i)&&t.push(i);r--;)i=I[r],i in n&&n[i]!==u[i]&&!m.contains(t,i)&&t.push(i)}var u=this,i=u._,o=Array.prototype,a=Object.prototype,c=Function.prototype,f=o.push,l=o.slice,s=a.toString,p=a.hasOwnProperty,h=Array.isArray,v=Object.keys,g=c.bind,y=Object.create,d=function(){},m=function(n){return n instanceof m?n:this instanceof m?void(this._wrapped=n):new m(n)};"undefined"!=typeof exports?("undefined"!=typeof module&&module.exports&&(exports=module.exports=m),exports._=m):u._=m,m.VERSION="1.8.3";var b=function(n,t,r){if(t===void 0)return n;switch(null==r?3:r){case 1:return function(r){return n.call(t,r)};case 2:return function(r,e){return n.call(t,r,e)};case 3:return function(r,e,u){return n.call(t,r,e,u)};case 4:return function(r,e,u,i){return n.call(t,r,e,u,i)}}return function(){return n.apply(t,arguments)}},x=function(n,t,r){return null==n?m.identity:m.isFunction(n)?b(n,t,r):m.isObject(n)?m.matcher(n):m.property(n)};m.iteratee=function(n,t){return x(n,t,1/0)};var _=function(n,t){return function(r){var e=arguments.length;if(2>e||null==r)return r;for(var u=1;e>u;u++)for(var i=arguments[u],o=n(i),a=o.length,c=0;a>c;c++){var f=o[c];t&&r[f]!==void 0||(r[f]=i[f])}return r}},j=function(n){if(!m.isObject(n))return{};if(y)return y(n);d.prototype=n;var t=new d;return d.prototype=null,t},w=function(n){return function(t){return null==t?void 0:t[n]}},A=Math.pow(2,53)-1,O=w("length"),k=function(n){var t=O(n);return"number"==typeof t&&t>=0&&A>=t};m.each=m.forEach=function(n,t,r){t=b(t,r);var e,u;if(k(n))for(e=0,u=n.length;u>e;e++)t(n[e],e,n);else{var i=m.keys(n);for(e=0,u=i.length;u>e;e++)t(n[i[e]],i[e],n)}return n},m.map=m.collect=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=Array(u),o=0;u>o;o++){var a=e?e[o]:o;i[o]=t(n[a],a,n)}return i},m.reduce=m.foldl=m.inject=n(1),m.reduceRight=m.foldr=n(-1),m.find=m.detect=function(n,t,r){var e;return e=k(n)?m.findIndex(n,t,r):m.findKey(n,t,r),e!==void 0&&e!==-1?n[e]:void 0},m.filter=m.select=function(n,t,r){var e=[];return t=x(t,r),m.each(n,function(n,r,u){t(n,r,u)&&e.push(n)}),e},m.reject=function(n,t,r){return m.filter(n,m.negate(x(t)),r)},m.every=m.all=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(!t(n[o],o,n))return!1}return!0},m.some=m.any=function(n,t,r){t=x(t,r);for(var e=!k(n)&&m.keys(n),u=(e||n).length,i=0;u>i;i++){var o=e?e[i]:i;if(t(n[o],o,n))return!0}return!1},m.contains=m.includes=m.include=function(n,t,r,e){return k(n)||(n=m.values(n)),("number"!=typeof r||e)&&(r=0),m.indexOf(n,t,r)>=0},m.invoke=function(n,t){var r=l.call(arguments,2),e=m.isFunction(t);return m.map(n,function(n){var u=e?t:n[t];return null==u?u:u.apply(n,r)})},m.pluck=function(n,t){return m.map(n,m.property(t))},m.where=function(n,t){return m.filter(n,m.matcher(t))},m.findWhere=function(n,t){return m.find(n,m.matcher(t))},m.max=function(n,t,r){var e,u,i=-1/0,o=-1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],e>i&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(u>o||u===-1/0&&i===-1/0)&&(i=n,o=u)});return i},m.min=function(n,t,r){var e,u,i=1/0,o=1/0;if(null==t&&null!=n){n=k(n)?n:m.values(n);for(var a=0,c=n.length;c>a;a++)e=n[a],i>e&&(i=e)}else t=x(t,r),m.each(n,function(n,r,e){u=t(n,r,e),(o>u||1/0===u&&1/0===i)&&(i=n,o=u)});return i},m.shuffle=function(n){for(var t,r=k(n)?n:m.values(n),e=r.length,u=Array(e),i=0;e>i;i++)t=m.random(0,i),t!==i&&(u[i]=u[t]),u[t]=r[i];return u},m.sample=function(n,t,r){return null==t||r?(k(n)||(n=m.values(n)),n[m.random(n.length-1)]):m.shuffle(n).slice(0,Math.max(0,t))},m.sortBy=function(n,t,r){return t=x(t,r),m.pluck(m.map(n,function(n,r,e){return{value:n,index:r,criteria:t(n,r,e)}}).sort(function(n,t){var r=n.criteria,e=t.criteria;if(r!==e){if(r>e||r===void 0)return 1;if(e>r||e===void 0)return-1}return n.index-t.index}),"value")};var F=function(n){return function(t,r,e){var u={};return r=x(r,e),m.each(t,function(e,i){var o=r(e,i,t);n(u,e,o)}),u}};m.groupBy=F(function(n,t,r){m.has(n,r)?n[r].push(t):n[r]=[t]}),m.indexBy=F(function(n,t,r){n[r]=t}),m.countBy=F(function(n,t,r){m.has(n,r)?n[r]++:n[r]=1}),m.toArray=function(n){return n?m.isArray(n)?l.call(n):k(n)?m.map(n,m.identity):m.values(n):[]},m.size=function(n){return null==n?0:k(n)?n.length:m.keys(n).length},m.partition=function(n,t,r){t=x(t,r);var e=[],u=[];return m.each(n,function(n,r,i){(t(n,r,i)?e:u).push(n)}),[e,u]},m.first=m.head=m.take=function(n,t,r){return null==n?void 0:null==t||r?n[0]:m.initial(n,n.length-t)},m.initial=function(n,t,r){return l.call(n,0,Math.max(0,n.length-(null==t||r?1:t)))},m.last=function(n,t,r){return null==n?void 0:null==t||r?n[n.length-1]:m.rest(n,Math.max(0,n.length-t))},m.rest=m.tail=m.drop=function(n,t,r){return l.call(n,null==t||r?1:t)},m.compact=function(n){return m.filter(n,m.identity)};var S=function(n,t,r,e){for(var u=[],i=0,o=e||0,a=O(n);a>o;o++){var c=n[o];if(k(c)&&(m.isArray(c)||m.isArguments(c))){t||(c=S(c,t,r));var f=0,l=c.length;for(u.length+=l;l>f;)u[i++]=c[f++]}else r||(u[i++]=c)}return u};m.flatten=function(n,t){return S(n,t,!1)},m.without=function(n){return m.difference(n,l.call(arguments,1))},m.uniq=m.unique=function(n,t,r,e){m.isBoolean(t)||(e=r,r=t,t=!1),null!=r&&(r=x(r,e));for(var u=[],i=[],o=0,a=O(n);a>o;o++){var c=n[o],f=r?r(c,o,n):c;t?(o&&i===f||u.push(c),i=f):r?m.contains(i,f)||(i.push(f),u.push(c)):m.contains(u,c)||u.push(c)}return u},m.union=function(){return m.uniq(S(arguments,!0,!0))},m.intersection=function(n){for(var t=[],r=arguments.length,e=0,u=O(n);u>e;e++){var i=n[e];if(!m.contains(t,i)){for(var o=1;r>o&&m.contains(arguments[o],i);o++);o===r&&t.push(i)}}return t},m.difference=function(n){var t=S(arguments,!0,!0,1);return m.filter(n,function(n){return!m.contains(t,n)})},m.zip=function(){return m.unzip(arguments)},m.unzip=function(n){for(var t=n&&m.max(n,O).length||0,r=Array(t),e=0;t>e;e++)r[e]=m.pluck(n,e);return r},m.object=function(n,t){for(var r={},e=0,u=O(n);u>e;e++)t?r[n[e]]=t[e]:r[n[e][0]]=n[e][1];return r},m.findIndex=t(1),m.findLastIndex=t(-1),m.sortedIndex=function(n,t,r,e){r=x(r,e,1);for(var u=r(t),i=0,o=O(n);o>i;){var a=Math.floor((i+o)/2);r(n[a])<u?i=a+1:o=a}return i},m.indexOf=r(1,m.findIndex,m.sortedIndex),m.lastIndexOf=r(-1,m.findLastIndex),m.range=function(n,t,r){null==t&&(t=n||0,n=0),r=r||1;for(var e=Math.max(Math.ceil((t-n)/r),0),u=Array(e),i=0;e>i;i++,n+=r)u[i]=n;return u};var E=function(n,t,r,e,u){if(!(e instanceof t))return n.apply(r,u);var i=j(n.prototype),o=n.apply(i,u);return m.isObject(o)?o:i};m.bind=function(n,t){if(g&&n.bind===g)return g.apply(n,l.call(arguments,1));if(!m.isFunction(n))throw new TypeError("Bind must be called on a function");var r=l.call(arguments,2),e=function(){return E(n,e,t,this,r.concat(l.call(arguments)))};return e},m.partial=function(n){var t=l.call(arguments,1),r=function(){for(var e=0,u=t.length,i=Array(u),o=0;u>o;o++)i[o]=t[o]===m?arguments[e++]:t[o];for(;e<arguments.length;)i.push(arguments[e++]);return E(n,r,this,this,i)};return r},m.bindAll=function(n){var t,r,e=arguments.length;if(1>=e)throw new Error("bindAll must be passed function names");for(t=1;e>t;t++)r=arguments[t],n[r]=m.bind(n[r],n);return n},m.memoize=function(n,t){var r=function(e){var u=r.cache,i=""+(t?t.apply(this,arguments):e);return m.has(u,i)||(u[i]=n.apply(this,arguments)),u[i]};return r.cache={},r},m.delay=function(n,t){var r=l.call(arguments,2);return setTimeout(function(){return n.apply(null,r)},t)},m.defer=m.partial(m.delay,m,1),m.throttle=function(n,t,r){var e,u,i,o=null,a=0;r||(r={});var c=function(){a=r.leading===!1?0:m.now(),o=null,i=n.apply(e,u),o||(e=u=null)};return function(){var f=m.now();a||r.leading!==!1||(a=f);var l=t-(f-a);return e=this,u=arguments,0>=l||l>t?(o&&(clearTimeout(o),o=null),a=f,i=n.apply(e,u),o||(e=u=null)):o||r.trailing===!1||(o=setTimeout(c,l)),i}},m.debounce=function(n,t,r){var e,u,i,o,a,c=function(){var f=m.now()-o;t>f&&f>=0?e=setTimeout(c,t-f):(e=null,r||(a=n.apply(i,u),e||(i=u=null)))};return function(){i=this,u=arguments,o=m.now();var f=r&&!e;return e||(e=setTimeout(c,t)),f&&(a=n.apply(i,u),i=u=null),a}},m.wrap=function(n,t){return m.partial(t,n)},m.negate=function(n){return function(){return!n.apply(this,arguments)}},m.compose=function(){var n=arguments,t=n.length-1;return function(){for(var r=t,e=n[t].apply(this,arguments);r--;)e=n[r].call(this,e);return e}},m.after=function(n,t){return function(){return--n<1?t.apply(this,arguments):void 0}},m.before=function(n,t){var r;return function(){return--n>0&&(r=t.apply(this,arguments)),1>=n&&(t=null),r}},m.once=m.partial(m.before,2);var M=!{toString:null}.propertyIsEnumerable("toString"),I=["valueOf","isPrototypeOf","toString","propertyIsEnumerable","hasOwnProperty","toLocaleString"];m.keys=function(n){if(!m.isObject(n))return[];if(v)return v(n);var t=[];for(var r in n)m.has(n,r)&&t.push(r);return M&&e(n,t),t},m.allKeys=function(n){if(!m.isObject(n))return[];var t=[];for(var r in n)t.push(r);return M&&e(n,t),t},m.values=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=n[t[u]];return e},m.mapObject=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=u.length,o={},a=0;i>a;a++)e=u[a],o[e]=t(n[e],e,n);return o},m.pairs=function(n){for(var t=m.keys(n),r=t.length,e=Array(r),u=0;r>u;u++)e[u]=[t[u],n[t[u]]];return e},m.invert=function(n){for(var t={},r=m.keys(n),e=0,u=r.length;u>e;e++)t[n[r[e]]]=r[e];return t},m.functions=m.methods=function(n){var t=[];for(var r in n)m.isFunction(n[r])&&t.push(r);return t.sort()},m.extend=_(m.allKeys),m.extendOwn=m.assign=_(m.keys),m.findKey=function(n,t,r){t=x(t,r);for(var e,u=m.keys(n),i=0,o=u.length;o>i;i++)if(e=u[i],t(n[e],e,n))return e},m.pick=function(n,t,r){var e,u,i={},o=n;if(null==o)return i;m.isFunction(t)?(u=m.allKeys(o),e=b(t,r)):(u=S(arguments,!1,!1,1),e=function(n,t,r){return t in r},o=Object(o));for(var a=0,c=u.length;c>a;a++){var f=u[a],l=o[f];e(l,f,o)&&(i[f]=l)}return i},m.omit=function(n,t,r){if(m.isFunction(t))t=m.negate(t);else{var e=m.map(S(arguments,!1,!1,1),String);t=function(n,t){return!m.contains(e,t)}}return m.pick(n,t,r)},m.defaults=_(m.allKeys,!0),m.create=function(n,t){var r=j(n);return t&&m.extendOwn(r,t),r},m.clone=function(n){return m.isObject(n)?m.isArray(n)?n.slice():m.extend({},n):n},m.tap=function(n,t){return t(n),n},m.isMatch=function(n,t){var r=m.keys(t),e=r.length;if(null==n)return!e;for(var u=Object(n),i=0;e>i;i++){var o=r[i];if(t[o]!==u[o]||!(o in u))return!1}return!0};var N=function(n,t,r,e){if(n===t)return 0!==n||1/n===1/t;if(null==n||null==t)return n===t;n instanceof m&&(n=n._wrapped),t instanceof m&&(t=t._wrapped);var u=s.call(n);if(u!==s.call(t))return!1;switch(u){case"[object RegExp]":case"[object String]":return""+n==""+t;case"[object Number]":return+n!==+n?+t!==+t:0===+n?1/+n===1/t:+n===+t;case"[object Date]":case"[object Boolean]":return+n===+t}var i="[object Array]"===u;if(!i){if("object"!=typeof n||"object"!=typeof t)return!1;var o=n.constructor,a=t.constructor;if(o!==a&&!(m.isFunction(o)&&o instanceof o&&m.isFunction(a)&&a instanceof a)&&"constructor"in n&&"constructor"in t)return!1}r=r||[],e=e||[];for(var c=r.length;c--;)if(r[c]===n)return e[c]===t;if(r.push(n),e.push(t),i){if(c=n.length,c!==t.length)return!1;for(;c--;)if(!N(n[c],t[c],r,e))return!1}else{var f,l=m.keys(n);if(c=l.length,m.keys(t).length!==c)return!1;for(;c--;)if(f=l[c],!m.has(t,f)||!N(n[f],t[f],r,e))return!1}return r.pop(),e.pop(),!0};m.isEqual=function(n,t){return N(n,t)},m.isEmpty=function(n){return null==n?!0:k(n)&&(m.isArray(n)||m.isString(n)||m.isArguments(n))?0===n.length:0===m.keys(n).length},m.isElement=function(n){return!(!n||1!==n.nodeType)},m.isArray=h||function(n){return"[object Array]"===s.call(n)},m.isObject=function(n){var t=typeof n;return"function"===t||"object"===t&&!!n},m.each(["Arguments","Function","String","Number","Date","RegExp","Error"],function(n){m["is"+n]=function(t){return s.call(t)==="[object "+n+"]"}}),m.isArguments(arguments)||(m.isArguments=function(n){return m.has(n,"callee")}),"function"!=typeof/./&&"object"!=typeof Int8Array&&(m.isFunction=function(n){return"function"==typeof n||!1}),m.isFinite=function(n){return isFinite(n)&&!isNaN(parseFloat(n))},m.isNaN=function(n){return m.isNumber(n)&&n!==+n},m.isBoolean=function(n){return n===!0||n===!1||"[object Boolean]"===s.call(n)},m.isNull=function(n){return null===n},m.isUndefined=function(n){return n===void 0},m.has=function(n,t){return null!=n&&p.call(n,t)},m.noConflict=function(){return u._=i,this},m.identity=function(n){return n},m.constant=function(n){return function(){return n}},m.noop=function(){},m.property=w,m.propertyOf=function(n){return null==n?function(){}:function(t){return n[t]}},m.matcher=m.matches=function(n){return n=m.extendOwn({},n),function(t){return m.isMatch(t,n)}},m.times=function(n,t,r){var e=Array(Math.max(0,n));t=b(t,r,1);for(var u=0;n>u;u++)e[u]=t(u);return e},m.random=function(n,t){return null==t&&(t=n,n=0),n+Math.floor(Math.random()*(t-n+1))},m.now=Date.now||function(){return(new Date).getTime()};var B={"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#x27;","`":"&#x60;"},T=m.invert(B),R=function(n){var t=function(t){return n[t]},r="(?:"+m.keys(n).join("|")+")",e=RegExp(r),u=RegExp(r,"g");return function(n){return n=null==n?"":""+n,e.test(n)?n.replace(u,t):n}};m.escape=R(B),m.unescape=R(T),m.result=function(n,t,r){var e=null==n?void 0:n[t];return e===void 0&&(e=r),m.isFunction(e)?e.call(n):e};var q=0;m.uniqueId=function(n){var t=++q+"";return n?n+t:t},m.templateSettings={evaluate:/<%([\s\S]+?)%>/g,interpolate:/<%=([\s\S]+?)%>/g,escape:/<%-([\s\S]+?)%>/g};var K=/(.)^/,z={"'":"'","\\":"\\","\r":"r","\n":"n","\u2028":"u2028","\u2029":"u2029"},D=/\\|'|\r|\n|\u2028|\u2029/g,L=function(n){return"\\"+z[n]};m.template=function(n,t,r){!t&&r&&(t=r),t=m.defaults({},t,m.templateSettings);var e=RegExp([(t.escape||K).source,(t.interpolate||K).source,(t.evaluate||K).source].join("|")+"|$","g"),u=0,i="__p+='";n.replace(e,function(t,r,e,o,a){return i+=n.slice(u,a).replace(D,L),u=a+t.length,r?i+="'+\n((__t=("+r+"))==null?'':_.escape(__t))+\n'":e?i+="'+\n((__t=("+e+"))==null?'':__t)+\n'":o&&(i+="';\n"+o+"\n__p+='"),t}),i+="';\n",t.variable||(i="with(obj||{}){\n"+i+"}\n"),i="var __t,__p='',__j=Array.prototype.join,"+"print=function(){__p+=__j.call(arguments,'');};\n"+i+"return __p;\n";try{var o=new Function(t.variable||"obj","_",i)}catch(a){throw a.source=i,a}var c=function(n){return o.call(this,n,m)},f=t.variable||"obj";return c.source="function("+f+"){\n"+i+"}",c},m.chain=function(n){var t=m(n);return t._chain=!0,t};var P=function(n,t){return n._chain?m(t).chain():t};m.mixin=function(n){m.each(m.functions(n),function(t){var r=m[t]=n[t];m.prototype[t]=function(){var n=[this._wrapped];return f.apply(n,arguments),P(this,r.apply(m,n))}})},m.mixin(m),m.each(["pop","push","reverse","shift","sort","splice","unshift"],function(n){var t=o[n];m.prototype[n]=function(){var r=this._wrapped;return t.apply(r,arguments),"shift"!==n&&"splice"!==n||0!==r.length||delete r[0],P(this,r)}}),m.each(["concat","join","slice"],function(n){var t=o[n];m.prototype[n]=function(){return P(this,t.apply(this._wrapped,arguments))}}),m.prototype.value=function(){return this._wrapped},m.prototype.valueOf=m.prototype.toJSON=m.prototype.value,m.prototype.toString=function(){return""+this._wrapped},"function"==typeof define&&define.amd&&define("underscore",[],function(){return m})}).call(this);
+//# sourceMappingURL=underscore-min.map
\ No newline at end of file
diff --git a/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/underscore.js b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/underscore.js
new file mode 100644
index 0000000..bddfdc9
--- /dev/null
+++ b/views/ngXosViews/serviceGrid/src/vendor/lodash/vendor/underscore/underscore.js
@@ -0,0 +1,1620 @@
+//     Underscore.js 1.8.3
+//     http://underscorejs.org
+//     (c) 2009-2016 Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors
+//     Underscore may be freely distributed under the MIT license.
+
+(function() {
+
+  // Baseline setup
+  // --------------
+
+  // Establish the root object, `window` (`self`) in the browser, `global`
+  // on the server, or `this` in some virtual machines. We use `self`
+  // instead of `window` for `WebWorker` support.
+  var root = typeof self == 'object' && self.self === self && self ||
+            typeof global == 'object' && global.global === global && global ||
+            this;
+
+  // Save the previous value of the `_` variable.
+  var previousUnderscore = root._;
+
+  // Save bytes in the minified (but not gzipped) version:
+  var ArrayProto = Array.prototype, ObjProto = Object.prototype;
+  var SymbolProto = typeof Symbol !== 'undefined' ? Symbol.prototype : null;
+
+  // Create quick reference variables for speed access to core prototypes.
+  var push = ArrayProto.push,
+      slice = ArrayProto.slice,
+      toString = ObjProto.toString,
+      hasOwnProperty = ObjProto.hasOwnProperty;
+
+  // All **ECMAScript 5** native function implementations that we hope to use
+  // are declared here.
+  var nativeIsArray = Array.isArray,
+      nativeKeys = Object.keys,
+      nativeCreate = Object.create;
+
+  // Naked function reference for surrogate-prototype-swapping.
+  var Ctor = function(){};
+
+  // Create a safe reference to the Underscore object for use below.
+  var _ = function(obj) {
+    if (obj instanceof _) return obj;
+    if (!(this instanceof _)) return new _(obj);
+    this._wrapped = obj;
+  };
+
+  // Export the Underscore object for **Node.js**, with
+  // backwards-compatibility for their old module API. If we're in
+  // the browser, add `_` as a global object.
+  // (`nodeType` is checked to ensure that `module`
+  // and `exports` are not HTML elements.)
+  if (typeof exports != 'undefined' && !exports.nodeType) {
+    if (typeof module != 'undefined' && !module.nodeType && module.exports) {
+      exports = module.exports = _;
+    }
+    exports._ = _;
+  } else {
+    root._ = _;
+  }
+
+  // Current version.
+  _.VERSION = '1.8.3';
+
+  // Internal function that returns an efficient (for current engines) version
+  // of the passed-in callback, to be repeatedly applied in other Underscore
+  // functions.
+  var optimizeCb = function(func, context, argCount) {
+    if (context === void 0) return func;
+    switch (argCount == null ? 3 : argCount) {
+      case 1: return function(value) {
+        return func.call(context, value);
+      };
+      // The 2-parameter case has been omitted only because no current consumers
+      // made use of it.
+      case 3: return function(value, index, collection) {
+        return func.call(context, value, index, collection);
+      };
+      case 4: return function(accumulator, value, index, collection) {
+        return func.call(context, accumulator, value, index, collection);
+      };
+    }
+    return function() {
+      return func.apply(context, arguments);
+    };
+  };
+
+  // An internal function to generate callbacks that can be applied to each
+  // element in a collection, returning the desired result — either `identity`,
+  // an arbitrary callback, a property matcher, or a property accessor.
+  var cb = function(value, context, argCount) {
+    if (value == null) return _.identity;
+    if (_.isFunction(value)) return optimizeCb(value, context, argCount);
+    if (_.isObject(value)) return _.matcher(value);
+    return _.property(value);
+  };
+
+  // An external wrapper for the internal callback generator.
+  _.iteratee = function(value, context) {
+    return cb(value, context, Infinity);
+  };
+
+  // Similar to ES6's rest param (http://ariya.ofilabs.com/2013/03/es6-and-rest-parameter.html)
+  // This accumulates the arguments passed into an array, after a given index.
+  var restArgs = function(func, startIndex) {
+    startIndex = startIndex == null ? func.length - 1 : +startIndex;
+    return function() {
+      var length = Math.max(arguments.length - startIndex, 0);
+      var rest = Array(length);
+      for (var index = 0; index < length; index++) {
+        rest[index] = arguments[index + startIndex];
+      }
+      switch (startIndex) {
+        case 0: return func.call(this, rest);
+        case 1: return func.call(this, arguments[0], rest);
+        case 2: return func.call(this, arguments[0], arguments[1], rest);
+      }
+      var args = Array(startIndex + 1);
+      for (index = 0; index < startIndex; index++) {
+        args[index] = arguments[index];
+      }
+      args[startIndex] = rest;
+      return func.apply(this, args);
+    };
+  };
+
+  // An internal function for creating a new object that inherits from another.
+  var baseCreate = function(prototype) {
+    if (!_.isObject(prototype)) return {};
+    if (nativeCreate) return nativeCreate(prototype);
+    Ctor.prototype = prototype;
+    var result = new Ctor;
+    Ctor.prototype = null;
+    return result;
+  };
+
+  var property = function(key) {
+    return function(obj) {
+      return obj == null ? void 0 : obj[key];
+    };
+  };
+
+  // Helper for collection methods to determine whether a collection
+  // should be iterated as an array or as an object.
+  // Related: http://people.mozilla.org/~jorendorff/es6-draft.html#sec-tolength
+  // Avoids a very nasty iOS 8 JIT bug on ARM-64. #2094
+  var MAX_ARRAY_INDEX = Math.pow(2, 53) - 1;
+  var getLength = property('length');
+  var isArrayLike = function(collection) {
+    var length = getLength(collection);
+    return typeof length == 'number' && length >= 0 && length <= MAX_ARRAY_INDEX;
+  };
+
+  // Collection Functions
+  // --------------------
+
+  // The cornerstone, an `each` implementation, aka `forEach`.
+  // Handles raw objects in addition to array-likes. Treats all
+  // sparse array-likes as if they were dense.
+  _.each = _.forEach = function(obj, iteratee, context) {
+    iteratee = optimizeCb(iteratee, context);
+    var i, length;
+    if (isArrayLike(obj)) {
+      for (i = 0, length = obj.length; i < length; i++) {
+        iteratee(obj[i], i, obj);
+      }
+    } else {
+      var keys = _.keys(obj);
+      for (i = 0, length = keys.length; i < length; i++) {
+        iteratee(obj[keys[i]], keys[i], obj);
+      }
+    }
+    return obj;
+  };
+
+  // Return the results of applying the iteratee to each element.
+  _.map = _.collect = function(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    var keys = !isArrayLike(obj) && _.keys(obj),
+        length = (keys || obj).length,
+        results = Array(length);
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      results[index] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  // Create a reducing function iterating left or right.
+  var createReduce = function(dir) {
+    // Wrap code that reassigns argument variables in a separate function than
+    // the one that accesses `arguments.length` to avoid a perf hit. (#1991)
+    var reducer = function(obj, iteratee, memo, initial) {
+      var keys = !isArrayLike(obj) && _.keys(obj),
+          length = (keys || obj).length,
+          index = dir > 0 ? 0 : length - 1;
+      if (!initial) {
+        memo = obj[keys ? keys[index] : index];
+        index += dir;
+      }
+      for (; index >= 0 && index < length; index += dir) {
+        var currentKey = keys ? keys[index] : index;
+        memo = iteratee(memo, obj[currentKey], currentKey, obj);
+      }
+      return memo;
+    };
+
+    return function(obj, iteratee, memo, context) {
+      var initial = arguments.length >= 3;
+      return reducer(obj, optimizeCb(iteratee, context, 4), memo, initial);
+    };
+  };
+
+  // **Reduce** builds up a single result from a list of values, aka `inject`,
+  // or `foldl`.
+  _.reduce = _.foldl = _.inject = createReduce(1);
+
+  // The right-associative version of reduce, also known as `foldr`.
+  _.reduceRight = _.foldr = createReduce(-1);
+
+  // Return the first value which passes a truth test. Aliased as `detect`.
+  _.find = _.detect = function(obj, predicate, context) {
+    var keyFinder = isArrayLike(obj) ? _.findIndex : _.findKey;
+    var key = keyFinder(obj, predicate, context);
+    if (key !== void 0 && key !== -1) return obj[key];
+  };
+
+  // Return all the elements that pass a truth test.
+  // Aliased as `select`.
+  _.filter = _.select = function(obj, predicate, context) {
+    var results = [];
+    predicate = cb(predicate, context);
+    _.each(obj, function(value, index, list) {
+      if (predicate(value, index, list)) results.push(value);
+    });
+    return results;
+  };
+
+  // Return all the elements for which a truth test fails.
+  _.reject = function(obj, predicate, context) {
+    return _.filter(obj, _.negate(cb(predicate)), context);
+  };
+
+  // Determine whether all of the elements match a truth test.
+  // Aliased as `all`.
+  _.every = _.all = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = !isArrayLike(obj) && _.keys(obj),
+        length = (keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      if (!predicate(obj[currentKey], currentKey, obj)) return false;
+    }
+    return true;
+  };
+
+  // Determine if at least one element in the object matches a truth test.
+  // Aliased as `any`.
+  _.some = _.any = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = !isArrayLike(obj) && _.keys(obj),
+        length = (keys || obj).length;
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys ? keys[index] : index;
+      if (predicate(obj[currentKey], currentKey, obj)) return true;
+    }
+    return false;
+  };
+
+  // Determine if the array or object contains a given item (using `===`).
+  // Aliased as `includes` and `include`.
+  _.contains = _.includes = _.include = function(obj, item, fromIndex, guard) {
+    if (!isArrayLike(obj)) obj = _.values(obj);
+    if (typeof fromIndex != 'number' || guard) fromIndex = 0;
+    return _.indexOf(obj, item, fromIndex) >= 0;
+  };
+
+  // Invoke a method (with arguments) on every item in a collection.
+  _.invoke = restArgs(function(obj, method, args) {
+    var isFunc = _.isFunction(method);
+    return _.map(obj, function(value) {
+      var func = isFunc ? method : value[method];
+      return func == null ? func : func.apply(value, args);
+    });
+  });
+
+  // Convenience version of a common use case of `map`: fetching a property.
+  _.pluck = function(obj, key) {
+    return _.map(obj, _.property(key));
+  };
+
+  // Convenience version of a common use case of `filter`: selecting only objects
+  // containing specific `key:value` pairs.
+  _.where = function(obj, attrs) {
+    return _.filter(obj, _.matcher(attrs));
+  };
+
+  // Convenience version of a common use case of `find`: getting the first object
+  // containing specific `key:value` pairs.
+  _.findWhere = function(obj, attrs) {
+    return _.find(obj, _.matcher(attrs));
+  };
+
+  // Return the maximum element (or element-based computation).
+  _.max = function(obj, iteratee, context) {
+    var result = -Infinity, lastComputed = -Infinity,
+        value, computed;
+    if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
+      obj = isArrayLike(obj) ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value != null && value > result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = cb(iteratee, context);
+      _.each(obj, function(v, index, list) {
+        computed = iteratee(v, index, list);
+        if (computed > lastComputed || computed === -Infinity && result === -Infinity) {
+          result = v;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Return the minimum element (or element-based computation).
+  _.min = function(obj, iteratee, context) {
+    var result = Infinity, lastComputed = Infinity,
+        value, computed;
+    if (iteratee == null || (typeof iteratee == 'number' && typeof obj[0] != 'object') && obj != null) {
+      obj = isArrayLike(obj) ? obj : _.values(obj);
+      for (var i = 0, length = obj.length; i < length; i++) {
+        value = obj[i];
+        if (value != null && value < result) {
+          result = value;
+        }
+      }
+    } else {
+      iteratee = cb(iteratee, context);
+      _.each(obj, function(v, index, list) {
+        computed = iteratee(v, index, list);
+        if (computed < lastComputed || computed === Infinity && result === Infinity) {
+          result = v;
+          lastComputed = computed;
+        }
+      });
+    }
+    return result;
+  };
+
+  // Shuffle a collection.
+  _.shuffle = function(obj) {
+    return _.sample(obj, Infinity);
+  };
+
+  // Sample **n** random values from a collection using the modern version of the
+  // [Fisher-Yates shuffle](http://en.wikipedia.org/wiki/Fisher–Yates_shuffle).
+  // If **n** is not specified, returns a single random element.
+  // The internal `guard` argument allows it to work with `map`.
+  _.sample = function(obj, n, guard) {
+    if (n == null || guard) {
+      if (!isArrayLike(obj)) obj = _.values(obj);
+      return obj[_.random(obj.length - 1)];
+    }
+    var sample = isArrayLike(obj) ? _.clone(obj) : _.values(obj);
+    var length = getLength(sample);
+    n = Math.max(Math.min(n, length), 0);
+    var last = length - 1;
+    for (var index = 0; index < n; index++) {
+      var rand = _.random(index, last);
+      var temp = sample[index];
+      sample[index] = sample[rand];
+      sample[rand] = temp;
+    }
+    return sample.slice(0, n);
+  };
+
+  // Sort the object's values by a criterion produced by an iteratee.
+  _.sortBy = function(obj, iteratee, context) {
+    var index = 0;
+    iteratee = cb(iteratee, context);
+    return _.pluck(_.map(obj, function(value, key, list) {
+      return {
+        value: value,
+        index: index++,
+        criteria: iteratee(value, key, list)
+      };
+    }).sort(function(left, right) {
+      var a = left.criteria;
+      var b = right.criteria;
+      if (a !== b) {
+        if (a > b || a === void 0) return 1;
+        if (a < b || b === void 0) return -1;
+      }
+      return left.index - right.index;
+    }), 'value');
+  };
+
+  // An internal function used for aggregate "group by" operations.
+  var group = function(behavior, partition) {
+    return function(obj, iteratee, context) {
+      var result = partition ? [[], []] : {};
+      iteratee = cb(iteratee, context);
+      _.each(obj, function(value, index) {
+        var key = iteratee(value, index, obj);
+        behavior(result, value, key);
+      });
+      return result;
+    };
+  };
+
+  // Groups the object's values by a criterion. Pass either a string attribute
+  // to group by, or a function that returns the criterion.
+  _.groupBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key].push(value); else result[key] = [value];
+  });
+
+  // Indexes the object's values by a criterion, similar to `groupBy`, but for
+  // when you know that your index values will be unique.
+  _.indexBy = group(function(result, value, key) {
+    result[key] = value;
+  });
+
+  // Counts instances of an object that group by a certain criterion. Pass
+  // either a string attribute to count by, or a function that returns the
+  // criterion.
+  _.countBy = group(function(result, value, key) {
+    if (_.has(result, key)) result[key]++; else result[key] = 1;
+  });
+
+  var reStrSymbol = /[^\ud800-\udfff]|[\ud800-\udbff][\udc00-\udfff]|[\ud800-\udfff]/g;
+  // Safely create a real, live array from anything iterable.
+  _.toArray = function(obj) {
+    if (!obj) return [];
+    if (_.isArray(obj)) return slice.call(obj);
+    if (_.isString(obj)) {
+      // Keep surrogate pair characters together
+      return obj.match(reStrSymbol);
+    }
+    if (isArrayLike(obj)) return _.map(obj);
+    return _.values(obj);
+  };
+
+  // Return the number of elements in an object.
+  _.size = function(obj) {
+    if (obj == null) return 0;
+    return isArrayLike(obj) ? obj.length : _.keys(obj).length;
+  };
+
+  // Split a collection into two arrays: one whose elements all satisfy the given
+  // predicate, and one whose elements all do not satisfy the predicate.
+  _.partition = group(function(result, value, pass) {
+    result[pass ? 0 : 1].push(value);
+  }, true);
+
+  // Array Functions
+  // ---------------
+
+  // Get the first element of an array. Passing **n** will return the first N
+  // values in the array. Aliased as `head` and `take`. The **guard** check
+  // allows it to work with `_.map`.
+  _.first = _.head = _.take = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[0];
+    return _.initial(array, array.length - n);
+  };
+
+  // Returns everything but the last entry of the array. Especially useful on
+  // the arguments object. Passing **n** will return all the values in
+  // the array, excluding the last N.
+  _.initial = function(array, n, guard) {
+    return slice.call(array, 0, Math.max(0, array.length - (n == null || guard ? 1 : n)));
+  };
+
+  // Get the last element of an array. Passing **n** will return the last N
+  // values in the array.
+  _.last = function(array, n, guard) {
+    if (array == null) return void 0;
+    if (n == null || guard) return array[array.length - 1];
+    return _.rest(array, Math.max(0, array.length - n));
+  };
+
+  // Returns everything but the first entry of the array. Aliased as `tail` and `drop`.
+  // Especially useful on the arguments object. Passing an **n** will return
+  // the rest N values in the array.
+  _.rest = _.tail = _.drop = function(array, n, guard) {
+    return slice.call(array, n == null || guard ? 1 : n);
+  };
+
+  // Trim out all falsy values from an array.
+  _.compact = function(array) {
+    return _.filter(array);
+  };
+
+  // Internal implementation of a recursive `flatten` function.
+  var flatten = function(input, shallow, strict, output) {
+    output = output || [];
+    var idx = output.length;
+    for (var i = 0, length = getLength(input); i < length; i++) {
+      var value = input[i];
+      if (isArrayLike(value) && (_.isArray(value) || _.isArguments(value))) {
+        // Flatten current level of array or arguments object.
+        if (shallow) {
+          var j = 0, len = value.length;
+          while (j < len) output[idx++] = value[j++];
+        } else {
+          flatten(value, shallow, strict, output);
+          idx = output.length;
+        }
+      } else if (!strict) {
+        output[idx++] = value;
+      }
+    }
+    return output;
+  };
+
+  // Flatten out an array, either recursively (by default), or just one level.
+  _.flatten = function(array, shallow) {
+    return flatten(array, shallow, false);
+  };
+
+  // Return a version of the array that does not contain the specified value(s).
+  _.without = restArgs(function(array, otherArrays) {
+    return _.difference(array, otherArrays);
+  });
+
+  // Produce a duplicate-free version of the array. If the array has already
+  // been sorted, you have the option of using a faster algorithm.
+  // Aliased as `unique`.
+  _.uniq = _.unique = function(array, isSorted, iteratee, context) {
+    if (!_.isBoolean(isSorted)) {
+      context = iteratee;
+      iteratee = isSorted;
+      isSorted = false;
+    }
+    if (iteratee != null) iteratee = cb(iteratee, context);
+    var result = [];
+    var seen = [];
+    for (var i = 0, length = getLength(array); i < length; i++) {
+      var value = array[i],
+          computed = iteratee ? iteratee(value, i, array) : value;
+      if (isSorted) {
+        if (!i || seen !== computed) result.push(value);
+        seen = computed;
+      } else if (iteratee) {
+        if (!_.contains(seen, computed)) {
+          seen.push(computed);
+          result.push(value);
+        }
+      } else if (!_.contains(result, value)) {
+        result.push(value);
+      }
+    }
+    return result;
+  };
+
+  // Produce an array that contains the union: each distinct element from all of
+  // the passed-in arrays.
+  _.union = restArgs(function(arrays) {
+    return _.uniq(flatten(arrays, true, true));
+  });
+
+  // Produce an array that contains every item shared between all the
+  // passed-in arrays.
+  _.intersection = function(array) {
+    var result = [];
+    var argsLength = arguments.length;
+    for (var i = 0, length = getLength(array); i < length; i++) {
+      var item = array[i];
+      if (_.contains(result, item)) continue;
+      var j;
+      for (j = 1; j < argsLength; j++) {
+        if (!_.contains(arguments[j], item)) break;
+      }
+      if (j === argsLength) result.push(item);
+    }
+    return result;
+  };
+
+  // Take the difference between one array and a number of other arrays.
+  // Only the elements present in just the first array will remain.
+  _.difference = restArgs(function(array, rest) {
+    rest = flatten(rest, true, true);
+    return _.filter(array, function(value){
+      return !_.contains(rest, value);
+    });
+  });
+
+  // Complement of _.zip. Unzip accepts an array of arrays and groups
+  // each array's elements on shared indices.
+  _.unzip = function(array) {
+    var length = array && _.max(array, getLength).length || 0;
+    var result = Array(length);
+
+    for (var index = 0; index < length; index++) {
+      result[index] = _.pluck(array, index);
+    }
+    return result;
+  };
+
+  // Zip together multiple lists into a single array -- elements that share
+  // an index go together.
+  _.zip = restArgs(_.unzip);
+
+  // Converts lists into objects. Pass either a single array of `[key, value]`
+  // pairs, or two parallel arrays of the same length -- one of keys, and one of
+  // the corresponding values.
+  _.object = function(list, values) {
+    var result = {};
+    for (var i = 0, length = getLength(list); i < length; i++) {
+      if (values) {
+        result[list[i]] = values[i];
+      } else {
+        result[list[i][0]] = list[i][1];
+      }
+    }
+    return result;
+  };
+
+  // Generator function to create the findIndex and findLastIndex functions.
+  var createPredicateIndexFinder = function(dir) {
+    return function(array, predicate, context) {
+      predicate = cb(predicate, context);
+      var length = getLength(array);
+      var index = dir > 0 ? 0 : length - 1;
+      for (; index >= 0 && index < length; index += dir) {
+        if (predicate(array[index], index, array)) return index;
+      }
+      return -1;
+    };
+  };
+
+  // Returns the first index on an array-like that passes a predicate test.
+  _.findIndex = createPredicateIndexFinder(1);
+  _.findLastIndex = createPredicateIndexFinder(-1);
+
+  // Use a comparator function to figure out the smallest index at which
+  // an object should be inserted so as to maintain order. Uses binary search.
+  _.sortedIndex = function(array, obj, iteratee, context) {
+    iteratee = cb(iteratee, context, 1);
+    var value = iteratee(obj);
+    var low = 0, high = getLength(array);
+    while (low < high) {
+      var mid = Math.floor((low + high) / 2);
+      if (iteratee(array[mid]) < value) low = mid + 1; else high = mid;
+    }
+    return low;
+  };
+
+  // Generator function to create the indexOf and lastIndexOf functions.
+  var createIndexFinder = function(dir, predicateFind, sortedIndex) {
+    return function(array, item, idx) {
+      var i = 0, length = getLength(array);
+      if (typeof idx == 'number') {
+        if (dir > 0) {
+          i = idx >= 0 ? idx : Math.max(idx + length, i);
+        } else {
+          length = idx >= 0 ? Math.min(idx + 1, length) : idx + length + 1;
+        }
+      } else if (sortedIndex && idx && length) {
+        idx = sortedIndex(array, item);
+        return array[idx] === item ? idx : -1;
+      }
+      if (item !== item) {
+        idx = predicateFind(slice.call(array, i, length), _.isNaN);
+        return idx >= 0 ? idx + i : -1;
+      }
+      for (idx = dir > 0 ? i : length - 1; idx >= 0 && idx < length; idx += dir) {
+        if (array[idx] === item) return idx;
+      }
+      return -1;
+    };
+  };
+
+  // Return the position of the first occurrence of an item in an array,
+  // or -1 if the item is not included in the array.
+  // If the array is large and already in sort order, pass `true`
+  // for **isSorted** to use binary search.
+  _.indexOf = createIndexFinder(1, _.findIndex, _.sortedIndex);
+  _.lastIndexOf = createIndexFinder(-1, _.findLastIndex);
+
+  // Generate an integer Array containing an arithmetic progression. A port of
+  // the native Python `range()` function. See
+  // [the Python documentation](http://docs.python.org/library/functions.html#range).
+  _.range = function(start, stop, step) {
+    if (stop == null) {
+      stop = start || 0;
+      start = 0;
+    }
+    if (!step) {
+      step = stop < start ? -1 : 1;
+    }
+
+    var length = Math.max(Math.ceil((stop - start) / step), 0);
+    var range = Array(length);
+
+    for (var idx = 0; idx < length; idx++, start += step) {
+      range[idx] = start;
+    }
+
+    return range;
+  };
+
+  // Split an **array** into several arrays containing **count** or less elements
+  // of initial array.
+  _.chunk = function(array, count) {
+    if (count == null || count < 1) return [];
+
+    var result = [];
+    var i = 0, length = array.length;
+    while (i < length) {
+      result.push(slice.call(array, i, i += count));
+    }
+    return result;
+  };
+
+  // Function (ahem) Functions
+  // ------------------
+
+  // Determines whether to execute a function as a constructor
+  // or a normal function with the provided arguments.
+  var executeBound = function(sourceFunc, boundFunc, context, callingContext, args) {
+    if (!(callingContext instanceof boundFunc)) return sourceFunc.apply(context, args);
+    var self = baseCreate(sourceFunc.prototype);
+    var result = sourceFunc.apply(self, args);
+    if (_.isObject(result)) return result;
+    return self;
+  };
+
+  // Create a function bound to a given object (assigning `this`, and arguments,
+  // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if
+  // available.
+  _.bind = restArgs(function(func, context, args) {
+    if (!_.isFunction(func)) throw new TypeError('Bind must be called on a function');
+    var bound = restArgs(function(callArgs) {
+      return executeBound(func, bound, context, this, args.concat(callArgs));
+    });
+    return bound;
+  });
+
+  // Partially apply a function by creating a version that has had some of its
+  // arguments pre-filled, without changing its dynamic `this` context. _ acts
+  // as a placeholder by default, allowing any combination of arguments to be
+  // pre-filled. Set `_.partial.placeholder` for a custom placeholder argument.
+  _.partial = restArgs(function(func, boundArgs) {
+    var placeholder = _.partial.placeholder;
+    var bound = function() {
+      var position = 0, length = boundArgs.length;
+      var args = Array(length);
+      for (var i = 0; i < length; i++) {
+        args[i] = boundArgs[i] === placeholder ? arguments[position++] : boundArgs[i];
+      }
+      while (position < arguments.length) args.push(arguments[position++]);
+      return executeBound(func, bound, this, this, args);
+    };
+    return bound;
+  });
+
+  _.partial.placeholder = _;
+
+  // Bind a number of an object's methods to that object. Remaining arguments
+  // are the method names to be bound. Useful for ensuring that all callbacks
+  // defined on an object belong to it.
+  _.bindAll = restArgs(function(obj, keys) {
+    keys = flatten(keys, false, false);
+    var index = keys.length;
+    if (index < 1) throw new Error('bindAll must be passed function names');
+    while (index--) {
+      var key = keys[index];
+      obj[key] = _.bind(obj[key], obj);
+    }
+  });
+
+  // Memoize an expensive function by storing its results.
+  _.memoize = function(func, hasher) {
+    var memoize = function(key) {
+      var cache = memoize.cache;
+      var address = '' + (hasher ? hasher.apply(this, arguments) : key);
+      if (!_.has(cache, address)) cache[address] = func.apply(this, arguments);
+      return cache[address];
+    };
+    memoize.cache = {};
+    return memoize;
+  };
+
+  // Delays a function for the given number of milliseconds, and then calls
+  // it with the arguments supplied.
+  _.delay = restArgs(function(func, wait, args) {
+    return setTimeout(function() {
+      return func.apply(null, args);
+    }, wait);
+  });
+
+  // Defers a function, scheduling it to run after the current call stack has
+  // cleared.
+  _.defer = _.partial(_.delay, _, 1);
+
+  // Returns a function, that, when invoked, will only be triggered at most once
+  // during a given window of time. Normally, the throttled function will run
+  // as much as it can, without ever going more than once per `wait` duration;
+  // but if you'd like to disable the execution on the leading edge, pass
+  // `{leading: false}`. To disable execution on the trailing edge, ditto.
+  _.throttle = function(func, wait, options) {
+    var timeout, context, args, result;
+    var previous = 0;
+    if (!options) options = {};
+
+    var later = function() {
+      previous = options.leading === false ? 0 : _.now();
+      timeout = null;
+      result = func.apply(context, args);
+      if (!timeout) context = args = null;
+    };
+
+    var throttled = function() {
+      var now = _.now();
+      if (!previous && options.leading === false) previous = now;
+      var remaining = wait - (now - previous);
+      context = this;
+      args = arguments;
+      if (remaining <= 0 || remaining > wait) {
+        if (timeout) {
+          clearTimeout(timeout);
+          timeout = null;
+        }
+        previous = now;
+        result = func.apply(context, args);
+        if (!timeout) context = args = null;
+      } else if (!timeout && options.trailing !== false) {
+        timeout = setTimeout(later, remaining);
+      }
+      return result;
+    };
+
+    throttled.cancel = function() {
+      clearTimeout(timeout);
+      previous = 0;
+      timeout = context = args = null;
+    };
+
+    return throttled;
+  };
+
+  // Returns a function, that, as long as it continues to be invoked, will not
+  // be triggered. The function will be called after it stops being called for
+  // N milliseconds. If `immediate` is passed, trigger the function on the
+  // leading edge, instead of the trailing.
+  _.debounce = function(func, wait, immediate) {
+    var timeout, result;
+
+    var later = function(context, args) {
+      timeout = null;
+      if (args) result = func.apply(context, args);
+    };
+
+    var debounced = restArgs(function(args) {
+      if (timeout) clearTimeout(timeout);
+      if (immediate) {
+        var callNow = !timeout;
+        timeout = setTimeout(later, wait);
+        if (callNow) result = func.apply(this, args);
+      } else {
+        timeout = _.delay(later, wait, this, args);
+      }
+
+      return result;
+    });
+
+    debounced.cancel = function() {
+      clearTimeout(timeout);
+      timeout = null;
+    };
+
+    return debounced;
+  };
+
+  // Returns the first function passed as an argument to the second,
+  // allowing you to adjust arguments, run code before and after, and
+  // conditionally execute the original function.
+  _.wrap = function(func, wrapper) {
+    return _.partial(wrapper, func);
+  };
+
+  // Returns a negated version of the passed-in predicate.
+  _.negate = function(predicate) {
+    return function() {
+      return !predicate.apply(this, arguments);
+    };
+  };
+
+  // Returns a function that is the composition of a list of functions, each
+  // consuming the return value of the function that follows.
+  _.compose = function() {
+    var args = arguments;
+    var start = args.length - 1;
+    return function() {
+      var i = start;
+      var result = args[start].apply(this, arguments);
+      while (i--) result = args[i].call(this, result);
+      return result;
+    };
+  };
+
+  // Returns a function that will only be executed on and after the Nth call.
+  _.after = function(times, func) {
+    return function() {
+      if (--times < 1) {
+        return func.apply(this, arguments);
+      }
+    };
+  };
+
+  // Returns a function that will only be executed up to (but not including) the Nth call.
+  _.before = function(times, func) {
+    var memo;
+    return function() {
+      if (--times > 0) {
+        memo = func.apply(this, arguments);
+      }
+      if (times <= 1) func = null;
+      return memo;
+    };
+  };
+
+  // Returns a function that will be executed at most one time, no matter how
+  // often you call it. Useful for lazy initialization.
+  _.once = _.partial(_.before, 2);
+
+  _.restArgs = restArgs;
+
+  // Object Functions
+  // ----------------
+
+  // Keys in IE < 9 that won't be iterated by `for key in ...` and thus missed.
+  var hasEnumBug = !{toString: null}.propertyIsEnumerable('toString');
+  var nonEnumerableProps = ['valueOf', 'isPrototypeOf', 'toString',
+                      'propertyIsEnumerable', 'hasOwnProperty', 'toLocaleString'];
+
+  var collectNonEnumProps = function(obj, keys) {
+    var nonEnumIdx = nonEnumerableProps.length;
+    var constructor = obj.constructor;
+    var proto = _.isFunction(constructor) && constructor.prototype || ObjProto;
+
+    // Constructor is a special case.
+    var prop = 'constructor';
+    if (_.has(obj, prop) && !_.contains(keys, prop)) keys.push(prop);
+
+    while (nonEnumIdx--) {
+      prop = nonEnumerableProps[nonEnumIdx];
+      if (prop in obj && obj[prop] !== proto[prop] && !_.contains(keys, prop)) {
+        keys.push(prop);
+      }
+    }
+  };
+
+  // Retrieve the names of an object's own properties.
+  // Delegates to **ECMAScript 5**'s native `Object.keys`.
+  _.keys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    if (nativeKeys) return nativeKeys(obj);
+    var keys = [];
+    for (var key in obj) if (_.has(obj, key)) keys.push(key);
+    // Ahem, IE < 9.
+    if (hasEnumBug) collectNonEnumProps(obj, keys);
+    return keys;
+  };
+
+  // Retrieve all the property names of an object.
+  _.allKeys = function(obj) {
+    if (!_.isObject(obj)) return [];
+    var keys = [];
+    for (var key in obj) keys.push(key);
+    // Ahem, IE < 9.
+    if (hasEnumBug) collectNonEnumProps(obj, keys);
+    return keys;
+  };
+
+  // Retrieve the values of an object's properties.
+  _.values = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var values = Array(length);
+    for (var i = 0; i < length; i++) {
+      values[i] = obj[keys[i]];
+    }
+    return values;
+  };
+
+  // Returns the results of applying the iteratee to each element of the object.
+  // In contrast to _.map it returns an object.
+  _.mapObject = function(obj, iteratee, context) {
+    iteratee = cb(iteratee, context);
+    var keys = _.keys(obj),
+        length = keys.length,
+        results = {};
+    for (var index = 0; index < length; index++) {
+      var currentKey = keys[index];
+      results[currentKey] = iteratee(obj[currentKey], currentKey, obj);
+    }
+    return results;
+  };
+
+  // Convert an object into a list of `[key, value]` pairs.
+  _.pairs = function(obj) {
+    var keys = _.keys(obj);
+    var length = keys.length;
+    var pairs = Array(length);
+    for (var i = 0; i < length; i++) {
+      pairs[i] = [keys[i], obj[keys[i]]];
+    }
+    return pairs;
+  };
+
+  // Invert the keys and values of an object. The values must be serializable.
+  _.invert = function(obj) {
+    var result = {};
+    var keys = _.keys(obj);
+    for (var i = 0, length = keys.length; i < length; i++) {
+      result[obj[keys[i]]] = keys[i];
+    }
+    return result;
+  };
+
+  // Return a sorted list of the function names available on the object.
+  // Aliased as `methods`.
+  _.functions = _.methods = function(obj) {
+    var names = [];
+    for (var key in obj) {
+      if (_.isFunction(obj[key])) names.push(key);
+    }
+    return names.sort();
+  };
+
+  // An internal function for creating assigner functions.
+  var createAssigner = function(keysFunc, defaults) {
+    return function(obj) {
+      var length = arguments.length;
+      if (defaults) obj = Object(obj);
+      if (length < 2 || obj == null) return obj;
+      for (var index = 1; index < length; index++) {
+        var source = arguments[index],
+            keys = keysFunc(source),
+            l = keys.length;
+        for (var i = 0; i < l; i++) {
+          var key = keys[i];
+          if (!defaults || obj[key] === void 0) obj[key] = source[key];
+        }
+      }
+      return obj;
+    };
+  };
+
+  // Extend a given object with all the properties in passed-in object(s).
+  _.extend = createAssigner(_.allKeys);
+
+  // Assigns a given object with all the own properties in the passed-in object(s).
+  // (https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/assign)
+  _.extendOwn = _.assign = createAssigner(_.keys);
+
+  // Returns the first key on an object that passes a predicate test.
+  _.findKey = function(obj, predicate, context) {
+    predicate = cb(predicate, context);
+    var keys = _.keys(obj), key;
+    for (var i = 0, length = keys.length; i < length; i++) {
+      key = keys[i];
+      if (predicate(obj[key], key, obj)) return key;
+    }
+  };
+
+  // Internal pick helper function to determine if `obj` has key `key`.
+  var keyInObj = function(value, key, obj) {
+    return key in obj;
+  };
+
+  // Return a copy of the object only containing the whitelisted properties.
+  _.pick = restArgs(function(obj, keys) {
+    var result = {}, iteratee = keys[0];
+    if (obj == null) return result;
+    if (_.isFunction(iteratee)) {
+      if (keys.length > 1) iteratee = optimizeCb(iteratee, keys[1]);
+      keys = _.allKeys(obj);
+    } else {
+      iteratee = keyInObj;
+      keys = flatten(keys, false, false);
+      obj = Object(obj);
+    }
+    for (var i = 0, length = keys.length; i < length; i++) {
+      var key = keys[i];
+      var value = obj[key];
+      if (iteratee(value, key, obj)) result[key] = value;
+    }
+    return result;
+  });
+
+   // Return a copy of the object without the blacklisted properties.
+  _.omit = restArgs(function(obj, keys) {
+    var iteratee = keys[0], context;
+    if (_.isFunction(iteratee)) {
+      iteratee = _.negate(iteratee);
+      if (keys.length > 1) context = keys[1];
+    } else {
+      keys = _.map(flatten(keys, false, false), String);
+      iteratee = function(value, key) {
+        return !_.contains(keys, key);
+      };
+    }
+    return _.pick(obj, iteratee, context);
+  });
+
+  // Fill in a given object with default properties.
+  _.defaults = createAssigner(_.allKeys, true);
+
+  // Creates an object that inherits from the given prototype object.
+  // If additional properties are provided then they will be added to the
+  // created object.
+  _.create = function(prototype, props) {
+    var result = baseCreate(prototype);
+    if (props) _.extendOwn(result, props);
+    return result;
+  };
+
+  // Create a (shallow-cloned) duplicate of an object.
+  _.clone = function(obj) {
+    if (!_.isObject(obj)) return obj;
+    return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
+  };
+
+  // Invokes interceptor with the obj, and then returns obj.
+  // The primary purpose of this method is to "tap into" a method chain, in
+  // order to perform operations on intermediate results within the chain.
+  _.tap = function(obj, interceptor) {
+    interceptor(obj);
+    return obj;
+  };
+
+  // Returns whether an object has a given set of `key:value` pairs.
+  _.isMatch = function(object, attrs) {
+    var keys = _.keys(attrs), length = keys.length;
+    if (object == null) return !length;
+    var obj = Object(object);
+    for (var i = 0; i < length; i++) {
+      var key = keys[i];
+      if (attrs[key] !== obj[key] || !(key in obj)) return false;
+    }
+    return true;
+  };
+
+
+  // Internal recursive comparison function for `isEqual`.
+  var eq, deepEq;
+  eq = function(a, b, aStack, bStack) {
+    // Identical objects are equal. `0 === -0`, but they aren't identical.
+    // See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
+    if (a === b) return a !== 0 || 1 / a === 1 / b;
+    // A strict comparison is necessary because `null == undefined`.
+    if (a == null || b == null) return a === b;
+    // `NaN`s are equivalent, but non-reflexive.
+    if (a !== a) return b !== b;
+    // Exhaust primitive checks
+    var type = typeof a;
+    if (type !== 'function' && type !== 'object' && typeof b != 'object') return false;
+    return deepEq(a, b, aStack, bStack);
+  };
+
+  // Internal recursive comparison function for `isEqual`.
+  deepEq = function(a, b, aStack, bStack) {
+    // Unwrap any wrapped objects.
+    if (a instanceof _) a = a._wrapped;
+    if (b instanceof _) b = b._wrapped;
+    // Compare `[[Class]]` names.
+    var className = toString.call(a);
+    if (className !== toString.call(b)) return false;
+    switch (className) {
+      // Strings, numbers, regular expressions, dates, and booleans are compared by value.
+      case '[object RegExp]':
+      // RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
+      case '[object String]':
+        // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
+        // equivalent to `new String("5")`.
+        return '' + a === '' + b;
+      case '[object Number]':
+        // `NaN`s are equivalent, but non-reflexive.
+        // Object(NaN) is equivalent to NaN.
+        if (+a !== +a) return +b !== +b;
+        // An `egal` comparison is performed for other numeric values.
+        return +a === 0 ? 1 / +a === 1 / b : +a === +b;
+      case '[object Date]':
+      case '[object Boolean]':
+        // Coerce dates and booleans to numeric primitive values. Dates are compared by their
+        // millisecond representations. Note that invalid dates with millisecond representations
+        // of `NaN` are not equivalent.
+        return +a === +b;
+      case '[object Symbol]':
+        return SymbolProto.valueOf.call(a) === SymbolProto.valueOf.call(b);
+    }
+
+    var areArrays = className === '[object Array]';
+    if (!areArrays) {
+      if (typeof a != 'object' || typeof b != 'object') return false;
+
+      // Objects with different constructors are not equivalent, but `Object`s or `Array`s
+      // from different frames are.
+      var aCtor = a.constructor, bCtor = b.constructor;
+      if (aCtor !== bCtor && !(_.isFunction(aCtor) && aCtor instanceof aCtor &&
+                               _.isFunction(bCtor) && bCtor instanceof bCtor)
+                          && ('constructor' in a && 'constructor' in b)) {
+        return false;
+      }
+    }
+    // Assume equality for cyclic structures. The algorithm for detecting cyclic
+    // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
+
+    // Initializing stack of traversed objects.
+    // It's done here since we only need them for objects and arrays comparison.
+    aStack = aStack || [];
+    bStack = bStack || [];
+    var length = aStack.length;
+    while (length--) {
+      // Linear search. Performance is inversely proportional to the number of
+      // unique nested structures.
+      if (aStack[length] === a) return bStack[length] === b;
+    }
+
+    // Add the first object to the stack of traversed objects.
+    aStack.push(a);
+    bStack.push(b);
+
+    // Recursively compare objects and arrays.
+    if (areArrays) {
+      // Compare array lengths to determine if a deep comparison is necessary.
+      length = a.length;
+      if (length !== b.length) return false;
+      // Deep compare the contents, ignoring non-numeric properties.
+      while (length--) {
+        if (!eq(a[length], b[length], aStack, bStack)) return false;
+      }
+    } else {
+      // Deep compare objects.
+      var keys = _.keys(a), key;
+      length = keys.length;
+      // Ensure that both objects contain the same number of properties before comparing deep equality.
+      if (_.keys(b).length !== length) return false;
+      while (length--) {
+        // Deep compare each member
+        key = keys[length];
+        if (!(_.has(b, key) && eq(a[key], b[key], aStack, bStack))) return false;
+      }
+    }
+    // Remove the first object from the stack of traversed objects.
+    aStack.pop();
+    bStack.pop();
+    return true;
+  };
+
+  // Perform a deep comparison to check if two objects are equal.
+  _.isEqual = function(a, b) {
+    return eq(a, b);
+  };
+
+  // Is a given array, string, or object empty?
+  // An "empty" object has no enumerable own-properties.
+  _.isEmpty = function(obj) {
+    if (obj == null) return true;
+    if (isArrayLike(obj) && (_.isArray(obj) || _.isString(obj) || _.isArguments(obj))) return obj.length === 0;
+    return _.keys(obj).length === 0;
+  };
+
+  // Is a given value a DOM element?
+  _.isElement = function(obj) {
+    return !!(obj && obj.nodeType === 1);
+  };
+
+  // Is a given value an array?
+  // Delegates to ECMA5's native Array.isArray
+  _.isArray = nativeIsArray || function(obj) {
+    return toString.call(obj) === '[object Array]';
+  };
+
+  // Is a given variable an object?
+  _.isObject = function(obj) {
+    var type = typeof obj;
+    return type === 'function' || type === 'object' && !!obj;
+  };
+
+  // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp, isError, isMap, isWeakMap, isSet, isWeakSet.
+  _.each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp', 'Error', 'Symbol', 'Map', 'WeakMap', 'Set', 'WeakSet'], function(name) {
+    _['is' + name] = function(obj) {
+      return toString.call(obj) === '[object ' + name + ']';
+    };
+  });
+
+  // Define a fallback version of the method in browsers (ahem, IE < 9), where
+  // there isn't any inspectable "Arguments" type.
+  if (!_.isArguments(arguments)) {
+    _.isArguments = function(obj) {
+      return _.has(obj, 'callee');
+    };
+  }
+
+  // Optimize `isFunction` if appropriate. Work around some typeof bugs in old v8,
+  // IE 11 (#1621), Safari 8 (#1929), and PhantomJS (#2236).
+  var nodelist = root.document && root.document.childNodes;
+  if (typeof /./ != 'function' && typeof Int8Array != 'object' && typeof nodelist != 'function') {
+    _.isFunction = function(obj) {
+      return typeof obj == 'function' || false;
+    };
+  }
+
+  // Is a given object a finite number?
+  _.isFinite = function(obj) {
+    return !_.isSymbol(obj) && isFinite(obj) && !isNaN(parseFloat(obj));
+  };
+
+  // Is the given value `NaN`?
+  _.isNaN = function(obj) {
+    return _.isNumber(obj) && isNaN(obj);
+  };
+
+  // Is a given value a boolean?
+  _.isBoolean = function(obj) {
+    return obj === true || obj === false || toString.call(obj) === '[object Boolean]';
+  };
+
+  // Is a given value equal to null?
+  _.isNull = function(obj) {
+    return obj === null;
+  };
+
+  // Is a given variable undefined?
+  _.isUndefined = function(obj) {
+    return obj === void 0;
+  };
+
+  // Shortcut function for checking if an object has a given property directly
+  // on itself (in other words, not on a prototype).
+  _.has = function(obj, key) {
+    return obj != null && hasOwnProperty.call(obj, key);
+  };
+
+  // Utility Functions
+  // -----------------
+
+  // Run Underscore.js in *noConflict* mode, returning the `_` variable to its
+  // previous owner. Returns a reference to the Underscore object.
+  _.noConflict = function() {
+    root._ = previousUnderscore;
+    return this;
+  };
+
+  // Keep the identity function around for default iteratees.
+  _.identity = function(value) {
+    return value;
+  };
+
+  // Predicate-generating functions. Often useful outside of Underscore.
+  _.constant = function(value) {
+    return function() {
+      return value;
+    };
+  };
+
+  _.noop = function(){};
+
+  _.property = property;
+
+  // Generates a function for a given object that returns a given property.
+  _.propertyOf = function(obj) {
+    return obj == null ? function(){} : function(key) {
+      return obj[key];
+    };
+  };
+
+  // Returns a predicate for checking whether an object has a given set of
+  // `key:value` pairs.
+  _.matcher = _.matches = function(attrs) {
+    attrs = _.extendOwn({}, attrs);
+    return function(obj) {
+      return _.isMatch(obj, attrs);
+    };
+  };
+
+  // Run a function **n** times.
+  _.times = function(n, iteratee, context) {
+    var accum = Array(Math.max(0, n));
+    iteratee = optimizeCb(iteratee, context, 1);
+    for (var i = 0; i < n; i++) accum[i] = iteratee(i);
+    return accum;
+  };
+
+  // Return a random integer between min and max (inclusive).
+  _.random = function(min, max) {
+    if (max == null) {
+      max = min;
+      min = 0;
+    }
+    return min + Math.floor(Math.random() * (max - min + 1));
+  };
+
+  // A (possibly faster) way to get the current timestamp as an integer.
+  _.now = Date.now || function() {
+    return new Date().getTime();
+  };
+
+   // List of HTML entities for escaping.
+  var escapeMap = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    "'": '&#x27;',
+    '`': '&#x60;'
+  };
+  var unescapeMap = _.invert(escapeMap);
+
+  // Functions for escaping and unescaping strings to/from HTML interpolation.
+  var createEscaper = function(map) {
+    var escaper = function(match) {
+      return map[match];
+    };
+    // Regexes for identifying a key that needs to be escaped.
+    var source = '(?:' + _.keys(map).join('|') + ')';
+    var testRegexp = RegExp(source);
+    var replaceRegexp = RegExp(source, 'g');
+    return function(string) {
+      string = string == null ? '' : '' + string;
+      return testRegexp.test(string) ? string.replace(replaceRegexp, escaper) : string;
+    };
+  };
+  _.escape = createEscaper(escapeMap);
+  _.unescape = createEscaper(unescapeMap);
+
+  // If the value of the named `property` is a function then invoke it with the
+  // `object` as context; otherwise, return it.
+  _.result = function(object, prop, fallback) {
+    var value = object == null ? void 0 : object[prop];
+    if (value === void 0) {
+      value = fallback;
+    }
+    return _.isFunction(value) ? value.call(object) : value;
+  };
+
+  // Generate a unique integer id (unique within the entire client session).
+  // Useful for temporary DOM ids.
+  var idCounter = 0;
+  _.uniqueId = function(prefix) {
+    var id = ++idCounter + '';
+    return prefix ? prefix + id : id;
+  };
+
+  // By default, Underscore uses ERB-style template delimiters, change the
+  // following template settings to use alternative delimiters.
+  _.templateSettings = {
+    evaluate: /<%([\s\S]+?)%>/g,
+    interpolate: /<%=([\s\S]+?)%>/g,
+    escape: /<%-([\s\S]+?)%>/g
+  };
+
+  // When customizing `templateSettings`, if you don't want to define an
+  // interpolation, evaluation or escaping regex, we need one that is
+  // guaranteed not to match.
+  var noMatch = /(.)^/;
+
+  // Certain characters need to be escaped so that they can be put into a
+  // string literal.
+  var escapes = {
+    "'": "'",
+    '\\': '\\',
+    '\r': 'r',
+    '\n': 'n',
+    '\u2028': 'u2028',
+    '\u2029': 'u2029'
+  };
+
+  var escapeRegExp = /\\|'|\r|\n|\u2028|\u2029/g;
+
+  var escapeChar = function(match) {
+    return '\\' + escapes[match];
+  };
+
+  // JavaScript micro-templating, similar to John Resig's implementation.
+  // Underscore templating handles arbitrary delimiters, preserves whitespace,
+  // and correctly escapes quotes within interpolated code.
+  // NB: `oldSettings` only exists for backwards compatibility.
+  _.template = function(text, settings, oldSettings) {
+    if (!settings && oldSettings) settings = oldSettings;
+    settings = _.defaults({}, settings, _.templateSettings);
+
+    // Combine delimiters into one regular expression via alternation.
+    var matcher = RegExp([
+      (settings.escape || noMatch).source,
+      (settings.interpolate || noMatch).source,
+      (settings.evaluate || noMatch).source
+    ].join('|') + '|$', 'g');
+
+    // Compile the template source, escaping string literals appropriately.
+    var index = 0;
+    var source = "__p+='";
+    text.replace(matcher, function(match, escape, interpolate, evaluate, offset) {
+      source += text.slice(index, offset).replace(escapeRegExp, escapeChar);
+      index = offset + match.length;
+
+      if (escape) {
+        source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'";
+      } else if (interpolate) {
+        source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'";
+      } else if (evaluate) {
+        source += "';\n" + evaluate + "\n__p+='";
+      }
+
+      // Adobe VMs need the match returned to produce the correct offset.
+      return match;
+    });
+    source += "';\n";
+
+    // If a variable is not specified, place data values in local scope.
+    if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n';
+
+    source = "var __t,__p='',__j=Array.prototype.join," +
+      "print=function(){__p+=__j.call(arguments,'');};\n" +
+      source + 'return __p;\n';
+
+    var render;
+    try {
+      render = new Function(settings.variable || 'obj', '_', source);
+    } catch (e) {
+      e.source = source;
+      throw e;
+    }
+
+    var template = function(data) {
+      return render.call(this, data, _);
+    };
+
+    // Provide the compiled source as a convenience for precompilation.
+    var argument = settings.variable || 'obj';
+    template.source = 'function(' + argument + '){\n' + source + '}';
+
+    return template;
+  };
+
+  // Add a "chain" function. Start chaining a wrapped Underscore object.
+  _.chain = function(obj) {
+    var instance = _(obj);
+    instance._chain = true;
+    return instance;
+  };
+
+  // OOP
+  // ---------------
+  // If Underscore is called as a function, it returns a wrapped object that
+  // can be used OO-style. This wrapper holds altered versions of all the
+  // underscore functions. Wrapped objects may be chained.
+
+  // Helper function to continue chaining intermediate results.
+  var chainResult = function(instance, obj) {
+    return instance._chain ? _(obj).chain() : obj;
+  };
+
+  // Add your own custom functions to the Underscore object.
+  _.mixin = function(obj) {
+    _.each(_.functions(obj), function(name) {
+      var func = _[name] = obj[name];
+      _.prototype[name] = function() {
+        var args = [this._wrapped];
+        push.apply(args, arguments);
+        return chainResult(this, func.apply(_, args));
+      };
+    });
+  };
+
+  // Add all of the Underscore functions to the wrapper object.
+  _.mixin(_);
+
+  // Add all mutator Array functions to the wrapper.
+  _.each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      var obj = this._wrapped;
+      method.apply(obj, arguments);
+      if ((name === 'shift' || name === 'splice') && obj.length === 0) delete obj[0];
+      return chainResult(this, obj);
+    };
+  });
+
+  // Add all accessor Array functions to the wrapper.
+  _.each(['concat', 'join', 'slice'], function(name) {
+    var method = ArrayProto[name];
+    _.prototype[name] = function() {
+      return chainResult(this, method.apply(this._wrapped, arguments));
+    };
+  });
+
+  // Extracts the result from a wrapped and chained object.
+  _.prototype.value = function() {
+    return this._wrapped;
+  };
+
+  // Provide unwrapping proxy for some methods used in engine operations
+  // such as arithmetic and JSON stringification.
+  _.prototype.valueOf = _.prototype.toJSON = _.prototype.value;
+
+  _.prototype.toString = function() {
+    return '' + this._wrapped;
+  };
+
+  // AMD registration happens at the end for compatibility with AMD loaders
+  // that may not enforce next-turn semantics on modules. Even though general
+  // practice for AMD registration is to be anonymous, underscore registers
+  // as a named module because, like jQuery, it is a base library that is
+  // popular enough to be bundled in a third party lib, but not be part of
+  // an AMD load request. Those cases could generate an error when an
+  // anonymous define() is called outside of a loader request.
+  if (typeof define == 'function' && define.amd) {
+    define('underscore', [], function() {
+      return _;
+    });
+  }
+}());