├── .gitignore ├── .jshintrc ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── lodash.custom.js ├── package-lock.json ├── package.json └── test └── log.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | npm-debug.log 4 | .sass-cache 5 | bower_components 6 | test/fixtures/files/* 7 | test/fixtures/js/* 8 | example.js 9 | .DS_store 10 | doc/ 11 | .coveralls.yml 12 | lodash.custom.min.js 13 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "curly": true, 3 | "eqeqeq": true, 4 | "immed": true, 5 | "latedef": "nofunc", 6 | "newcap": true, 7 | "noarg": true, 8 | "plusplus": true, 9 | "sub": true, 10 | "undef": true, 11 | "quotmark": "double", 12 | "unused": true, 13 | "boss": true, 14 | "indent": 4, 15 | "eqnull": true, 16 | "node": true, 17 | "strict": false, 18 | "mocha": true, 19 | "esversion": 6 20 | } 21 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.10' 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [2015] [Shane Osbourne] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ##eazy-logger [![Build Status](https://travis-ci.org/shakyShane/easy-logger.svg?branch=master)](https://travis-ci.org/shakyShane/easy-logger) [![Coverage Status](https://img.shields.io/coveralls/shakyShane/easy-logger.svg)](https://coveralls.io/r/shakyShane/easy-logger?branch=master) 2 | 3 | [tFunk](https://github.com/shakyShane/tfunk) + [String Substitution](http://nodejs.org/docs/latest/api/console.html#console_console_log_data) 4 | 5 | ![preview](http://cl.ly/image/3K2E2d111l15/Screen%20Shot%202014-09-17%20at%2020.49.17.png) 6 | 7 | ##Install 8 | 9 | ``` 10 | $ npm install eazy-logger --save 11 | ``` 12 | 13 | ##Usage 14 | 15 | ```js 16 | var logger = require("eazy-logger").Logger({ 17 | prefix: "{blue:[}{magenta:easy-logger}{blue:] }", 18 | useLevelPrefixes: true 19 | }); 20 | ``` 21 | 22 | ```js 23 | /** 24 | * Standard loggers + prefixes 25 | */ 26 | logger.debug("Debugging Msg"); 27 | logger.info("Info statement"); 28 | logger.warn("A little warning with string %s", "substitution"); 29 | logger.error("an error occurred in file: {red:%s}", "/users/awesomedev/file.js"); 30 | ``` 31 | 32 | ```js 33 | /** 34 | * Use string substitution + colours 35 | */ 36 | logger.log("error", "Use {green:built-in} %s", "String substitution"); 37 | ``` 38 | 39 | ```js 40 | /** 41 | * Set an option for the next log statement only 42 | */ 43 | logger.setOnce("useLevelPrefixes", true).warn("Use {green:built-in} %s", "String substitution"); 44 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * tFunk for colours/compiler 3 | */ 4 | var chalk = require("chalk"); 5 | 6 | /** 7 | * Lodash clonedeep & merge 8 | */ 9 | var _ = require("./lodash.custom"); 10 | 11 | /** 12 | * Default configuration. 13 | * Can be overridden in first constructor arg 14 | */ 15 | var defaults = { 16 | 17 | /** 18 | * Initial log level 19 | */ 20 | level: "info", 21 | 22 | /** 23 | * Prefix for logger 24 | */ 25 | prefix: "", 26 | 27 | /** 28 | * Available levels and their score 29 | */ 30 | levels: { 31 | "trace": 100, 32 | "debug": 200, 33 | "warn": 300, 34 | "info": 400, 35 | "error": 500 36 | }, 37 | 38 | /** 39 | * Default prefixes 40 | */ 41 | prefixes: { 42 | "trace": "[trace] ", 43 | "debug": chalk.yellow("[debug] "), 44 | "info": chalk.cyan("[info] "), 45 | "warn": chalk.magenta("[warn] "), 46 | "error": chalk.red("[error] ") 47 | }, 48 | 49 | /** 50 | * Should easy log statement be prefixed with the level? 51 | */ 52 | useLevelPrefixes: false 53 | }; 54 | 55 | 56 | /** 57 | * @param {Object} config 58 | * @constructor 59 | */ 60 | var Logger = function(config) { 61 | 62 | if (!(this instanceof Logger)) { 63 | return new Logger(config); 64 | } 65 | 66 | config = config || {}; 67 | 68 | this._mute = false; 69 | var safeConfig = {}; 70 | for (var attr in config) { 71 | if (!config.hasOwnProperty(attr)) { 72 | continue; 73 | } 74 | if (attr === "__proto__" || attr === "constructor" || attr === "prototype") { 75 | continue; 76 | } 77 | safeConfig[attr] = config[attr]; 78 | } 79 | this.config = _.merge({}, defaults, safeConfig); 80 | this.addLevelMethods(this.config.levels); 81 | this._memo = {}; 82 | 83 | return this; 84 | }; 85 | 86 | /** 87 | * Set an option once 88 | * @param path 89 | * @param value 90 | */ 91 | Logger.prototype.setOnce = function (path, value) { 92 | 93 | if (typeof this.config[path] !== "undefined") { 94 | 95 | if (typeof this._memo[path] === "undefined") { 96 | this._memo[path] = this.config[path]; 97 | } 98 | 99 | this.config[path] = value; 100 | } 101 | 102 | return this; 103 | }; 104 | /** 105 | * Add convenience method such as 106 | * logger.warn("msg") 107 | * logger.error("msg") 108 | * logger.info("msg") 109 | * 110 | * instead of 111 | * logger.log("warn", "msg"); 112 | * @param items 113 | */ 114 | Logger.prototype.addLevelMethods = function (items) { 115 | Object.keys(items).forEach(function (item) { 116 | if (!this[item]) { 117 | this[item] = function () { 118 | var args = Array.prototype.slice.call(arguments); 119 | this.log.apply(this, args); 120 | return this; 121 | }.bind(this, item); 122 | } 123 | }, this); 124 | }; 125 | /** 126 | * Reset the state of the logger. 127 | * @returns {Logger} 128 | */ 129 | Logger.prototype.reset = function () { 130 | 131 | this.setLevel(defaults.level) 132 | .setLevelPrefixes(defaults.useLevelPrefixes) 133 | .mute(false); 134 | 135 | return this; 136 | }; 137 | 138 | /** 139 | * @param {String} level 140 | * @returns {boolean} 141 | */ 142 | Logger.prototype.canLog = function (level) { 143 | return this.config.levels[level] >= this.config.levels[this.config.level] && !this._mute; 144 | }; 145 | 146 | /** 147 | * Log to the console with prefix 148 | * @param {String} level 149 | * @param {String} msg 150 | * @returns {Logger} 151 | */ 152 | Logger.prototype.log = function (level, msg) { 153 | 154 | var args = Array.prototype.slice.call(arguments); 155 | 156 | this.logOne(args, msg, level); 157 | 158 | return this; 159 | }; 160 | 161 | /** 162 | * Set the log level 163 | * @param {String} level 164 | * @returns {Logger} 165 | */ 166 | Logger.prototype.setLevel = function (level) { 167 | 168 | this.config.level = level; 169 | 170 | return this; 171 | }; 172 | 173 | /** 174 | * @param {boolean} state 175 | * @returns {Logger} 176 | */ 177 | Logger.prototype.setLevelPrefixes = function (state) { 178 | 179 | this.config.useLevelPrefixes = state; 180 | 181 | return this; 182 | }; 183 | 184 | /** 185 | * @param prefix 186 | */ 187 | Logger.prototype.setPrefix = function (prefix) { 188 | this.config.prefix = strOrFn(prefix); 189 | }; 190 | 191 | /** 192 | * @param {String} level 193 | * @param {String} msg 194 | * @returns {Logger} 195 | */ 196 | Logger.prototype.unprefixed = function (level, msg) { 197 | 198 | var args = Array.prototype.slice.call(arguments); 199 | 200 | this.logOne(args, msg, level, true); 201 | 202 | return this; 203 | }; 204 | 205 | /** 206 | * @param {Array} args 207 | * @param {()=>String} msg 208 | * @param {String} level 209 | * @param {boolean} [unprefixed] 210 | * @returns {Logger} 211 | */ 212 | Logger.prototype.logOne = function (args, msg, level, unprefixed) { 213 | 214 | if (!this.canLog(level)) { 215 | return; 216 | } 217 | 218 | args = args.slice(2); 219 | 220 | var incomingMessage = typeof msg === "string" ? msg : msg(); 221 | 222 | if (this.config.useLevelPrefixes && !unprefixed) { 223 | incomingMessage = this.config.prefixes[level] + incomingMessage; 224 | } 225 | 226 | var prefix = strOrFn(this.config.prefix); 227 | var result = unprefixed ? [incomingMessage] : [prefix, incomingMessage]; 228 | 229 | args.unshift(result.join("")); 230 | 231 | console.log.apply(console, args); 232 | 233 | this.resetTemps(); 234 | 235 | return this; 236 | }; 237 | 238 | /** 239 | * Reset any temporary value 240 | */ 241 | Logger.prototype.resetTemps = function () { 242 | 243 | Object.keys(this._memo).forEach(function (key) { 244 | this.config[key] = this._memo[key]; 245 | }, this); 246 | }; 247 | 248 | /** 249 | * Mute the logger 250 | */ 251 | Logger.prototype.mute = function (bool) { 252 | 253 | this._mute = bool; 254 | return this; 255 | }; 256 | 257 | /** 258 | * Clone the instance to share setup 259 | * @param opts 260 | * @returns {Logger} 261 | */ 262 | Logger.prototype.clone = function (opts) { 263 | 264 | var config = _.cloneDeep(this.config); 265 | 266 | if (typeof opts === "function") { 267 | config = opts(config) || {}; 268 | } else { 269 | config = _.merge({}, config, opts || {}); 270 | } 271 | 272 | return new Logger(config); 273 | }; 274 | 275 | /** 276 | * @param input 277 | */ 278 | function strOrFn(input) { 279 | if (typeof input === "string") { 280 | return input; 281 | } 282 | if (typeof input === "function") { 283 | return input(); 284 | } 285 | throw new Error("unreachable"); 286 | } 287 | 288 | module.exports.Logger = Logger; 289 | -------------------------------------------------------------------------------- /lodash.custom.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * lodash (Custom Build) 4 | * Build: `lodash include="cloneDeep,merge" exports="node"` 5 | * Copyright jQuery Foundation and other contributors 6 | * Released under MIT license 7 | * Based on Underscore.js 1.8.3 8 | * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors 9 | */ 10 | ;(function() { 11 | 12 | /** Used as a safe reference for `undefined` in pre-ES5 environments. */ 13 | var undefined; 14 | 15 | /** Used as the semantic version number. */ 16 | var VERSION = '4.12.0'; 17 | 18 | /** Used as the size to enable large array optimizations. */ 19 | var LARGE_ARRAY_SIZE = 200; 20 | 21 | /** Used as the `TypeError` message for "Functions" methods. */ 22 | var FUNC_ERROR_TEXT = 'Expected a function'; 23 | 24 | /** Used to stand-in for `undefined` hash values. */ 25 | var HASH_UNDEFINED = '__lodash_hash_undefined__'; 26 | 27 | /** Used as references for various `Number` constants. */ 28 | var INFINITY = 1 / 0, 29 | MAX_SAFE_INTEGER = 9007199254740991, 30 | MAX_INTEGER = 1.7976931348623157e+308, 31 | NAN = 0 / 0; 32 | 33 | /** `Object#toString` result references. */ 34 | var argsTag = '[object Arguments]', 35 | arrayTag = '[object Array]', 36 | boolTag = '[object Boolean]', 37 | dateTag = '[object Date]', 38 | errorTag = '[object Error]', 39 | funcTag = '[object Function]', 40 | genTag = '[object GeneratorFunction]', 41 | mapTag = '[object Map]', 42 | numberTag = '[object Number]', 43 | objectTag = '[object Object]', 44 | promiseTag = '[object Promise]', 45 | regexpTag = '[object RegExp]', 46 | setTag = '[object Set]', 47 | stringTag = '[object String]', 48 | symbolTag = '[object Symbol]', 49 | weakMapTag = '[object WeakMap]'; 50 | 51 | var arrayBufferTag = '[object ArrayBuffer]', 52 | dataViewTag = '[object DataView]', 53 | float32Tag = '[object Float32Array]', 54 | float64Tag = '[object Float64Array]', 55 | int8Tag = '[object Int8Array]', 56 | int16Tag = '[object Int16Array]', 57 | int32Tag = '[object Int32Array]', 58 | uint8Tag = '[object Uint8Array]', 59 | uint8ClampedTag = '[object Uint8ClampedArray]', 60 | uint16Tag = '[object Uint16Array]', 61 | uint32Tag = '[object Uint32Array]'; 62 | 63 | /** 64 | * Used to match `RegExp` 65 | * [syntax characters](http://ecma-international.org/ecma-262/6.0/#sec-patterns). 66 | */ 67 | var reRegExpChar = /[\\^$.*+?()[\]{}|]/g; 68 | 69 | /** Used to match leading and trailing whitespace. */ 70 | var reTrim = /^\s+|\s+$/g; 71 | 72 | /** Used to match `RegExp` flags from their coerced string values. */ 73 | var reFlags = /\w*$/; 74 | 75 | /** Used to detect bad signed hexadecimal string values. */ 76 | var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; 77 | 78 | /** Used to detect binary string values. */ 79 | var reIsBinary = /^0b[01]+$/i; 80 | 81 | /** Used to detect host constructors (Safari). */ 82 | var reIsHostCtor = /^\[object .+?Constructor\]$/; 83 | 84 | /** Used to detect octal string values. */ 85 | var reIsOctal = /^0o[0-7]+$/i; 86 | 87 | /** Used to detect unsigned integer values. */ 88 | var reIsUint = /^(?:0|[1-9]\d*)$/; 89 | 90 | /** Used to identify `toStringTag` values of typed arrays. */ 91 | var typedArrayTags = {}; 92 | typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = 93 | typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = 94 | typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = 95 | typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = 96 | typedArrayTags[uint32Tag] = true; 97 | typedArrayTags[argsTag] = typedArrayTags[arrayTag] = 98 | typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = 99 | typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = 100 | typedArrayTags[errorTag] = typedArrayTags[funcTag] = 101 | typedArrayTags[mapTag] = typedArrayTags[numberTag] = 102 | typedArrayTags[objectTag] = typedArrayTags[regexpTag] = 103 | typedArrayTags[setTag] = typedArrayTags[stringTag] = 104 | typedArrayTags[weakMapTag] = false; 105 | 106 | /** Used to identify `toStringTag` values supported by `_.clone`. */ 107 | var cloneableTags = {}; 108 | cloneableTags[argsTag] = cloneableTags[arrayTag] = 109 | cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = 110 | cloneableTags[boolTag] = cloneableTags[dateTag] = 111 | cloneableTags[float32Tag] = cloneableTags[float64Tag] = 112 | cloneableTags[int8Tag] = cloneableTags[int16Tag] = 113 | cloneableTags[int32Tag] = cloneableTags[mapTag] = 114 | cloneableTags[numberTag] = cloneableTags[objectTag] = 115 | cloneableTags[regexpTag] = cloneableTags[setTag] = 116 | cloneableTags[stringTag] = cloneableTags[symbolTag] = 117 | cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = 118 | cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; 119 | cloneableTags[errorTag] = cloneableTags[funcTag] = 120 | cloneableTags[weakMapTag] = false; 121 | 122 | /** Used to determine if values are of the language type `Object`. */ 123 | var objectTypes = { 124 | 'function': true, 125 | 'object': true 126 | }; 127 | 128 | /** Built-in method references without a dependency on `root`. */ 129 | var freeParseInt = parseInt; 130 | 131 | /** Detect free variable `exports`. */ 132 | var freeExports = (objectTypes[typeof exports] && exports && !exports.nodeType) 133 | ? exports 134 | : undefined; 135 | 136 | /** Detect free variable `module`. */ 137 | var freeModule = (objectTypes[typeof module] && module && !module.nodeType) 138 | ? module 139 | : undefined; 140 | 141 | /** Detect the popular CommonJS extension `module.exports`. */ 142 | var moduleExports = (freeModule && freeModule.exports === freeExports) 143 | ? freeExports 144 | : undefined; 145 | 146 | /** Detect free variable `global` from Node.js. */ 147 | var freeGlobal = checkGlobal(freeExports && freeModule && typeof global == 'object' && global); 148 | 149 | /** Detect free variable `self`. */ 150 | var freeSelf = checkGlobal(objectTypes[typeof self] && self); 151 | 152 | /** Detect free variable `window`. */ 153 | var freeWindow = checkGlobal(objectTypes[typeof window] && window); 154 | 155 | /** Detect `this` as the global object. */ 156 | var thisGlobal = checkGlobal(objectTypes[typeof this] && this); 157 | 158 | /** 159 | * Used as a reference to the global object. 160 | * 161 | * The `this` value is used if it's the global object to avoid Greasemonkey's 162 | * restricted `window` object, otherwise the `window` object is used. 163 | */ 164 | var root = freeGlobal || 165 | ((freeWindow !== (thisGlobal && thisGlobal.window)) && freeWindow) || 166 | freeSelf || thisGlobal || Function('return this')(); 167 | 168 | /*--------------------------------------------------------------------------*/ 169 | 170 | /** 171 | * Adds the key-value `pair` to `map`. 172 | * 173 | * @private 174 | * @param {Object} map The map to modify. 175 | * @param {Array} pair The key-value pair to add. 176 | * @returns {Object} Returns `map`. 177 | */ 178 | function addMapEntry(map, pair) { 179 | // Don't return `Map#set` because it doesn't return the map instance in IE 11. 180 | map.set(pair[0], pair[1]); 181 | return map; 182 | } 183 | 184 | /** 185 | * Adds `value` to `set`. 186 | * 187 | * @private 188 | * @param {Object} set The set to modify. 189 | * @param {*} value The value to add. 190 | * @returns {Object} Returns `set`. 191 | */ 192 | function addSetEntry(set, value) { 193 | set.add(value); 194 | return set; 195 | } 196 | 197 | /** 198 | * A faster alternative to `Function#apply`, this function invokes `func` 199 | * with the `this` binding of `thisArg` and the arguments of `args`. 200 | * 201 | * @private 202 | * @param {Function} func The function to invoke. 203 | * @param {*} thisArg The `this` binding of `func`. 204 | * @param {Array} args The arguments to invoke `func` with. 205 | * @returns {*} Returns the result of `func`. 206 | */ 207 | function apply(func, thisArg, args) { 208 | var length = args.length; 209 | switch (length) { 210 | case 0: return func.call(thisArg); 211 | case 1: return func.call(thisArg, args[0]); 212 | case 2: return func.call(thisArg, args[0], args[1]); 213 | case 3: return func.call(thisArg, args[0], args[1], args[2]); 214 | } 215 | return func.apply(thisArg, args); 216 | } 217 | 218 | /** 219 | * A specialized version of `_.forEach` for arrays without support for 220 | * iteratee shorthands. 221 | * 222 | * @private 223 | * @param {Array} array The array to iterate over. 224 | * @param {Function} iteratee The function invoked per iteration. 225 | * @returns {Array} Returns `array`. 226 | */ 227 | function arrayEach(array, iteratee) { 228 | var index = -1, 229 | length = array.length; 230 | 231 | while (++index < length) { 232 | if (iteratee(array[index], index, array) === false) { 233 | break; 234 | } 235 | } 236 | return array; 237 | } 238 | 239 | /** 240 | * Appends the elements of `values` to `array`. 241 | * 242 | * @private 243 | * @param {Array} array The array to modify. 244 | * @param {Array} values The values to append. 245 | * @returns {Array} Returns `array`. 246 | */ 247 | function arrayPush(array, values) { 248 | var index = -1, 249 | length = values.length, 250 | offset = array.length; 251 | 252 | while (++index < length) { 253 | array[offset + index] = values[index]; 254 | } 255 | return array; 256 | } 257 | 258 | /** 259 | * A specialized version of `_.reduce` for arrays without support for 260 | * iteratee shorthands. 261 | * 262 | * @private 263 | * @param {Array} array The array to iterate over. 264 | * @param {Function} iteratee The function invoked per iteration. 265 | * @param {*} [accumulator] The initial value. 266 | * @param {boolean} [initAccum] Specify using the first element of `array` as 267 | * the initial value. 268 | * @returns {*} Returns the accumulated value. 269 | */ 270 | function arrayReduce(array, iteratee, accumulator, initAccum) { 271 | var index = -1, 272 | length = array.length; 273 | 274 | if (initAccum && length) { 275 | accumulator = array[++index]; 276 | } 277 | while (++index < length) { 278 | accumulator = iteratee(accumulator, array[index], index, array); 279 | } 280 | return accumulator; 281 | } 282 | 283 | /** 284 | * The base implementation of `_.times` without support for iteratee shorthands 285 | * or max array length checks. 286 | * 287 | * @private 288 | * @param {number} n The number of times to invoke `iteratee`. 289 | * @param {Function} iteratee The function invoked per iteration. 290 | * @returns {Array} Returns the array of results. 291 | */ 292 | function baseTimes(n, iteratee) { 293 | var index = -1, 294 | result = Array(n); 295 | 296 | while (++index < n) { 297 | result[index] = iteratee(index); 298 | } 299 | return result; 300 | } 301 | 302 | /** 303 | * Checks if `value` is a global object. 304 | * 305 | * @private 306 | * @param {*} value The value to check. 307 | * @returns {null|Object} Returns `value` if it's a global object, else `null`. 308 | */ 309 | function checkGlobal(value) { 310 | return (value && value.Object === Object) ? value : null; 311 | } 312 | 313 | /** 314 | * Checks if `value` is a host object in IE < 9. 315 | * 316 | * @private 317 | * @param {*} value The value to check. 318 | * @returns {boolean} Returns `true` if `value` is a host object, else `false`. 319 | */ 320 | function isHostObject(value) { 321 | // Many host objects are `Object` objects that can coerce to strings 322 | // despite having improperly defined `toString` methods. 323 | var result = false; 324 | if (value != null && typeof value.toString != 'function') { 325 | try { 326 | result = !!(value + ''); 327 | } catch (e) {} 328 | } 329 | return result; 330 | } 331 | 332 | /** 333 | * Converts `iterator` to an array. 334 | * 335 | * @private 336 | * @param {Object} iterator The iterator to convert. 337 | * @returns {Array} Returns the converted array. 338 | */ 339 | function iteratorToArray(iterator) { 340 | var data, 341 | result = []; 342 | 343 | while (!(data = iterator.next()).done) { 344 | result.push(data.value); 345 | } 346 | return result; 347 | } 348 | 349 | /** 350 | * Converts `map` to its key-value pairs. 351 | * 352 | * @private 353 | * @param {Object} map The map to convert. 354 | * @returns {Array} Returns the key-value pairs. 355 | */ 356 | function mapToArray(map) { 357 | var index = -1, 358 | result = Array(map.size); 359 | 360 | map.forEach(function(value, key) { 361 | result[++index] = [key, value]; 362 | }); 363 | return result; 364 | } 365 | 366 | /** 367 | * Converts `set` to an array of its values. 368 | * 369 | * @private 370 | * @param {Object} set The set to convert. 371 | * @returns {Array} Returns the values. 372 | */ 373 | function setToArray(set) { 374 | var index = -1, 375 | result = Array(set.size); 376 | 377 | set.forEach(function(value) { 378 | result[++index] = value; 379 | }); 380 | return result; 381 | } 382 | 383 | /*--------------------------------------------------------------------------*/ 384 | 385 | /** Used for built-in method references. */ 386 | var arrayProto = Array.prototype, 387 | objectProto = Object.prototype; 388 | 389 | /** Used to resolve the decompiled source of functions. */ 390 | var funcToString = Function.prototype.toString; 391 | 392 | /** Used to check objects for own properties. */ 393 | var hasOwnProperty = objectProto.hasOwnProperty; 394 | 395 | /** Used to infer the `Object` constructor. */ 396 | var objectCtorString = funcToString.call(Object); 397 | 398 | /** 399 | * Used to resolve the 400 | * [`toStringTag`](http://ecma-international.org/ecma-262/6.0/#sec-object.prototype.tostring) 401 | * of values. 402 | */ 403 | var objectToString = objectProto.toString; 404 | 405 | /** Used to detect if a method is native. */ 406 | var reIsNative = RegExp('^' + 407 | funcToString.call(hasOwnProperty).replace(reRegExpChar, '\\$&') 408 | .replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, '$1.*?') + '$' 409 | ); 410 | 411 | /** Built-in value references. */ 412 | var Buffer = moduleExports ? root.Buffer : undefined, 413 | Reflect = root.Reflect, 414 | Symbol = root.Symbol, 415 | Uint8Array = root.Uint8Array, 416 | enumerate = Reflect ? Reflect.enumerate : undefined, 417 | getOwnPropertySymbols = Object.getOwnPropertySymbols, 418 | objectCreate = Object.create, 419 | propertyIsEnumerable = objectProto.propertyIsEnumerable, 420 | splice = arrayProto.splice; 421 | 422 | /* Built-in method references for those with the same name as other `lodash` methods. */ 423 | var nativeGetPrototype = Object.getPrototypeOf, 424 | nativeKeys = Object.keys, 425 | nativeMax = Math.max; 426 | 427 | /* Built-in method references that are verified to be native. */ 428 | var DataView = getNative(root, 'DataView'), 429 | Map = getNative(root, 'Map'), 430 | Promise = getNative(root, 'Promise'), 431 | Set = getNative(root, 'Set'), 432 | WeakMap = getNative(root, 'WeakMap'), 433 | nativeCreate = getNative(Object, 'create'); 434 | 435 | /** Used to lookup unminified function names. */ 436 | var realNames = {}; 437 | 438 | /** Used to detect maps, sets, and weakmaps. */ 439 | var dataViewCtorString = toSource(DataView), 440 | mapCtorString = toSource(Map), 441 | promiseCtorString = toSource(Promise), 442 | setCtorString = toSource(Set), 443 | weakMapCtorString = toSource(WeakMap); 444 | 445 | /** Used to convert symbols to primitives and strings. */ 446 | var symbolProto = Symbol ? Symbol.prototype : undefined, 447 | symbolValueOf = symbolProto ? symbolProto.valueOf : undefined; 448 | 449 | /*------------------------------------------------------------------------*/ 450 | 451 | /** 452 | * Creates a `lodash` object which wraps `value` to enable implicit method 453 | * chain sequences. Methods that operate on and return arrays, collections, 454 | * and functions can be chained together. Methods that retrieve a single value 455 | * or may return a primitive value will automatically end the chain sequence 456 | * and return the unwrapped value. Otherwise, the value must be unwrapped 457 | * with `_#value`. 458 | * 459 | * Explicit chain sequences, which must be unwrapped with `_#value`, may be 460 | * enabled using `_.chain`. 461 | * 462 | * The execution of chained methods is lazy, that is, it's deferred until 463 | * `_#value` is implicitly or explicitly called. 464 | * 465 | * Lazy evaluation allows several methods to support shortcut fusion. 466 | * Shortcut fusion is an optimization to merge iteratee calls; this avoids 467 | * the creation of intermediate arrays and can greatly reduce the number of 468 | * iteratee executions. Sections of a chain sequence qualify for shortcut 469 | * fusion if the section is applied to an array of at least `200` elements 470 | * and any iteratees accept only one argument. The heuristic for whether a 471 | * section qualifies for shortcut fusion is subject to change. 472 | * 473 | * Chaining is supported in custom builds as long as the `_#value` method is 474 | * directly or indirectly included in the build. 475 | * 476 | * In addition to lodash methods, wrappers have `Array` and `String` methods. 477 | * 478 | * The wrapper `Array` methods are: 479 | * `concat`, `join`, `pop`, `push`, `shift`, `sort`, `splice`, and `unshift` 480 | * 481 | * The wrapper `String` methods are: 482 | * `replace` and `split` 483 | * 484 | * The wrapper methods that support shortcut fusion are: 485 | * `at`, `compact`, `drop`, `dropRight`, `dropWhile`, `filter`, `find`, 486 | * `findLast`, `head`, `initial`, `last`, `map`, `reject`, `reverse`, `slice`, 487 | * `tail`, `take`, `takeRight`, `takeRightWhile`, `takeWhile`, and `toArray` 488 | * 489 | * The chainable wrapper methods are: 490 | * `after`, `ary`, `assign`, `assignIn`, `assignInWith`, `assignWith`, `at`, 491 | * `before`, `bind`, `bindAll`, `bindKey`, `castArray`, `chain`, `chunk`, 492 | * `commit`, `compact`, `concat`, `conforms`, `constant`, `countBy`, `create`, 493 | * `curry`, `debounce`, `defaults`, `defaultsDeep`, `defer`, `delay`, 494 | * `difference`, `differenceBy`, `differenceWith`, `drop`, `dropRight`, 495 | * `dropRightWhile`, `dropWhile`, `extend`, `extendWith`, `fill`, `filter`, 496 | * `flatMap`, `flatMapDeep`, `flatMapDepth`, `flatten`, `flattenDeep`, 497 | * `flattenDepth`, `flip`, `flow`, `flowRight`, `fromPairs`, `functions`, 498 | * `functionsIn`, `groupBy`, `initial`, `intersection`, `intersectionBy`, 499 | * `intersectionWith`, `invert`, `invertBy`, `invokeMap`, `iteratee`, `keyBy`, 500 | * `keys`, `keysIn`, `map`, `mapKeys`, `mapValues`, `matches`, `matchesProperty`, 501 | * `memoize`, `merge`, `mergeWith`, `method`, `methodOf`, `mixin`, `negate`, 502 | * `nthArg`, `omit`, `omitBy`, `once`, `orderBy`, `over`, `overArgs`, 503 | * `overEvery`, `overSome`, `partial`, `partialRight`, `partition`, `pick`, 504 | * `pickBy`, `plant`, `property`, `propertyOf`, `pull`, `pullAll`, `pullAllBy`, 505 | * `pullAllWith`, `pullAt`, `push`, `range`, `rangeRight`, `rearg`, `reject`, 506 | * `remove`, `rest`, `reverse`, `sampleSize`, `set`, `setWith`, `shuffle`, 507 | * `slice`, `sort`, `sortBy`, `splice`, `spread`, `tail`, `take`, `takeRight`, 508 | * `takeRightWhile`, `takeWhile`, `tap`, `throttle`, `thru`, `toArray`, 509 | * `toPairs`, `toPairsIn`, `toPath`, `toPlainObject`, `transform`, `unary`, 510 | * `union`, `unionBy`, `unionWith`, `uniq`, `uniqBy`, `uniqWith`, `unset`, 511 | * `unshift`, `unzip`, `unzipWith`, `update`, `updateWith`, `values`, 512 | * `valuesIn`, `without`, `wrap`, `xor`, `xorBy`, `xorWith`, `zip`, 513 | * `zipObject`, `zipObjectDeep`, and `zipWith` 514 | * 515 | * The wrapper methods that are **not** chainable by default are: 516 | * `add`, `attempt`, `camelCase`, `capitalize`, `ceil`, `clamp`, `clone`, 517 | * `cloneDeep`, `cloneDeepWith`, `cloneWith`, `deburr`, `divide`, `each`, 518 | * `eachRight`, `endsWith`, `eq`, `escape`, `escapeRegExp`, `every`, `find`, 519 | * `findIndex`, `findKey`, `findLast`, `findLastIndex`, `findLastKey`, `first`, 520 | * `floor`, `forEach`, `forEachRight`, `forIn`, `forInRight`, `forOwn`, 521 | * `forOwnRight`, `get`, `gt`, `gte`, `has`, `hasIn`, `head`, `identity`, 522 | * `includes`, `indexOf`, `inRange`, `invoke`, `isArguments`, `isArray`, 523 | * `isArrayBuffer`, `isArrayLike`, `isArrayLikeObject`, `isBoolean`, 524 | * `isBuffer`, `isDate`, `isElement`, `isEmpty`, `isEqual`, `isEqualWith`, 525 | * `isError`, `isFinite`, `isFunction`, `isInteger`, `isLength`, `isMap`, 526 | * `isMatch`, `isMatchWith`, `isNaN`, `isNative`, `isNil`, `isNull`, `isNumber`, 527 | * `isObject`, `isObjectLike`, `isPlainObject`, `isRegExp`, `isSafeInteger`, 528 | * `isSet`, `isString`, `isUndefined`, `isTypedArray`, `isWeakMap`, `isWeakSet`, 529 | * `join`, `kebabCase`, `last`, `lastIndexOf`, `lowerCase`, `lowerFirst`, 530 | * `lt`, `lte`, `max`, `maxBy`, `mean`, `meanBy`, `min`, `minBy`, `multiply`, 531 | * `noConflict`, `noop`, `now`, `nth`, `pad`, `padEnd`, `padStart`, `parseInt`, 532 | * `pop`, `random`, `reduce`, `reduceRight`, `repeat`, `result`, `round`, 533 | * `runInContext`, `sample`, `shift`, `size`, `snakeCase`, `some`, `sortedIndex`, 534 | * `sortedIndexBy`, `sortedLastIndex`, `sortedLastIndexBy`, `startCase`, 535 | * `startsWith`, `subtract`, `sum`, `sumBy`, `template`, `times`, `toFinite`, 536 | * `toInteger`, `toJSON`, `toLength`, `toLower`, `toNumber`, `toSafeInteger`, 537 | * `toString`, `toUpper`, `trim`, `trimEnd`, `trimStart`, `truncate`, `unescape`, 538 | * `uniqueId`, `upperCase`, `upperFirst`, `value`, and `words` 539 | * 540 | * @name _ 541 | * @constructor 542 | * @category Seq 543 | * @param {*} value The value to wrap in a `lodash` instance. 544 | * @returns {Object} Returns the new `lodash` wrapper instance. 545 | * @example 546 | * 547 | * function square(n) { 548 | * return n * n; 549 | * } 550 | * 551 | * var wrapped = _([1, 2, 3]); 552 | * 553 | * // Returns an unwrapped value. 554 | * wrapped.reduce(_.add); 555 | * // => 6 556 | * 557 | * // Returns a wrapped value. 558 | * var squares = wrapped.map(square); 559 | * 560 | * _.isArray(squares); 561 | * // => false 562 | * 563 | * _.isArray(squares.value()); 564 | * // => true 565 | */ 566 | function lodash() { 567 | // No operation performed. 568 | } 569 | 570 | /*------------------------------------------------------------------------*/ 571 | 572 | /** 573 | * Creates a hash object. 574 | * 575 | * @private 576 | * @constructor 577 | * @param {Array} [entries] The key-value pairs to cache. 578 | */ 579 | function Hash(entries) { 580 | var index = -1, 581 | length = entries ? entries.length : 0; 582 | 583 | this.clear(); 584 | while (++index < length) { 585 | var entry = entries[index]; 586 | this.set(entry[0], entry[1]); 587 | } 588 | } 589 | 590 | /** 591 | * Removes all key-value entries from the hash. 592 | * 593 | * @private 594 | * @name clear 595 | * @memberOf Hash 596 | */ 597 | function hashClear() { 598 | this.__data__ = nativeCreate ? nativeCreate(null) : {}; 599 | } 600 | 601 | /** 602 | * Removes `key` and its value from the hash. 603 | * 604 | * @private 605 | * @name delete 606 | * @memberOf Hash 607 | * @param {Object} hash The hash to modify. 608 | * @param {string} key The key of the value to remove. 609 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 610 | */ 611 | function hashDelete(key) { 612 | return this.has(key) && delete this.__data__[key]; 613 | } 614 | 615 | /** 616 | * Gets the hash value for `key`. 617 | * 618 | * @private 619 | * @name get 620 | * @memberOf Hash 621 | * @param {string} key The key of the value to get. 622 | * @returns {*} Returns the entry value. 623 | */ 624 | function hashGet(key) { 625 | var data = this.__data__; 626 | if (nativeCreate) { 627 | var result = data[key]; 628 | return result === HASH_UNDEFINED ? undefined : result; 629 | } 630 | return hasOwnProperty.call(data, key) ? data[key] : undefined; 631 | } 632 | 633 | /** 634 | * Checks if a hash value for `key` exists. 635 | * 636 | * @private 637 | * @name has 638 | * @memberOf Hash 639 | * @param {string} key The key of the entry to check. 640 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 641 | */ 642 | function hashHas(key) { 643 | var data = this.__data__; 644 | return nativeCreate ? data[key] !== undefined : hasOwnProperty.call(data, key); 645 | } 646 | 647 | /** 648 | * Sets the hash `key` to `value`. 649 | * 650 | * @private 651 | * @name set 652 | * @memberOf Hash 653 | * @param {string} key The key of the value to set. 654 | * @param {*} value The value to set. 655 | * @returns {Object} Returns the hash instance. 656 | */ 657 | function hashSet(key, value) { 658 | var data = this.__data__; 659 | data[key] = (nativeCreate && value === undefined) ? HASH_UNDEFINED : value; 660 | return this; 661 | } 662 | 663 | // Add methods to `Hash`. 664 | Hash.prototype.clear = hashClear; 665 | Hash.prototype['delete'] = hashDelete; 666 | Hash.prototype.get = hashGet; 667 | Hash.prototype.has = hashHas; 668 | Hash.prototype.set = hashSet; 669 | 670 | /*------------------------------------------------------------------------*/ 671 | 672 | /** 673 | * Creates an list cache object. 674 | * 675 | * @private 676 | * @constructor 677 | * @param {Array} [entries] The key-value pairs to cache. 678 | */ 679 | function ListCache(entries) { 680 | var index = -1, 681 | length = entries ? entries.length : 0; 682 | 683 | this.clear(); 684 | while (++index < length) { 685 | var entry = entries[index]; 686 | this.set(entry[0], entry[1]); 687 | } 688 | } 689 | 690 | /** 691 | * Removes all key-value entries from the list cache. 692 | * 693 | * @private 694 | * @name clear 695 | * @memberOf ListCache 696 | */ 697 | function listCacheClear() { 698 | this.__data__ = []; 699 | } 700 | 701 | /** 702 | * Removes `key` and its value from the list cache. 703 | * 704 | * @private 705 | * @name delete 706 | * @memberOf ListCache 707 | * @param {string} key The key of the value to remove. 708 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 709 | */ 710 | function listCacheDelete(key) { 711 | var data = this.__data__, 712 | index = assocIndexOf(data, key); 713 | 714 | if (index < 0) { 715 | return false; 716 | } 717 | var lastIndex = data.length - 1; 718 | if (index == lastIndex) { 719 | data.pop(); 720 | } else { 721 | splice.call(data, index, 1); 722 | } 723 | return true; 724 | } 725 | 726 | /** 727 | * Gets the list cache value for `key`. 728 | * 729 | * @private 730 | * @name get 731 | * @memberOf ListCache 732 | * @param {string} key The key of the value to get. 733 | * @returns {*} Returns the entry value. 734 | */ 735 | function listCacheGet(key) { 736 | var data = this.__data__, 737 | index = assocIndexOf(data, key); 738 | 739 | return index < 0 ? undefined : data[index][1]; 740 | } 741 | 742 | /** 743 | * Checks if a list cache value for `key` exists. 744 | * 745 | * @private 746 | * @name has 747 | * @memberOf ListCache 748 | * @param {string} key The key of the entry to check. 749 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 750 | */ 751 | function listCacheHas(key) { 752 | return assocIndexOf(this.__data__, key) > -1; 753 | } 754 | 755 | /** 756 | * Sets the list cache `key` to `value`. 757 | * 758 | * @private 759 | * @name set 760 | * @memberOf ListCache 761 | * @param {string} key The key of the value to set. 762 | * @param {*} value The value to set. 763 | * @returns {Object} Returns the list cache instance. 764 | */ 765 | function listCacheSet(key, value) { 766 | var data = this.__data__, 767 | index = assocIndexOf(data, key); 768 | 769 | if (index < 0) { 770 | data.push([key, value]); 771 | } else { 772 | data[index][1] = value; 773 | } 774 | return this; 775 | } 776 | 777 | // Add methods to `ListCache`. 778 | ListCache.prototype.clear = listCacheClear; 779 | ListCache.prototype['delete'] = listCacheDelete; 780 | ListCache.prototype.get = listCacheGet; 781 | ListCache.prototype.has = listCacheHas; 782 | ListCache.prototype.set = listCacheSet; 783 | 784 | /*------------------------------------------------------------------------*/ 785 | 786 | /** 787 | * Creates a map cache object to store key-value pairs. 788 | * 789 | * @private 790 | * @constructor 791 | * @param {Array} [entries] The key-value pairs to cache. 792 | */ 793 | function MapCache(entries) { 794 | var index = -1, 795 | length = entries ? entries.length : 0; 796 | 797 | this.clear(); 798 | while (++index < length) { 799 | var entry = entries[index]; 800 | this.set(entry[0], entry[1]); 801 | } 802 | } 803 | 804 | /** 805 | * Removes all key-value entries from the map. 806 | * 807 | * @private 808 | * @name clear 809 | * @memberOf MapCache 810 | */ 811 | function mapCacheClear() { 812 | this.__data__ = { 813 | 'hash': new Hash, 814 | 'map': new (Map || ListCache), 815 | 'string': new Hash 816 | }; 817 | } 818 | 819 | /** 820 | * Removes `key` and its value from the map. 821 | * 822 | * @private 823 | * @name delete 824 | * @memberOf MapCache 825 | * @param {string} key The key of the value to remove. 826 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 827 | */ 828 | function mapCacheDelete(key) { 829 | return getMapData(this, key)['delete'](key); 830 | } 831 | 832 | /** 833 | * Gets the map value for `key`. 834 | * 835 | * @private 836 | * @name get 837 | * @memberOf MapCache 838 | * @param {string} key The key of the value to get. 839 | * @returns {*} Returns the entry value. 840 | */ 841 | function mapCacheGet(key) { 842 | return getMapData(this, key).get(key); 843 | } 844 | 845 | /** 846 | * Checks if a map value for `key` exists. 847 | * 848 | * @private 849 | * @name has 850 | * @memberOf MapCache 851 | * @param {string} key The key of the entry to check. 852 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 853 | */ 854 | function mapCacheHas(key) { 855 | return getMapData(this, key).has(key); 856 | } 857 | 858 | /** 859 | * Sets the map `key` to `value`. 860 | * 861 | * @private 862 | * @name set 863 | * @memberOf MapCache 864 | * @param {string} key The key of the value to set. 865 | * @param {*} value The value to set. 866 | * @returns {Object} Returns the map cache instance. 867 | */ 868 | function mapCacheSet(key, value) { 869 | getMapData(this, key).set(key, value); 870 | return this; 871 | } 872 | 873 | // Add methods to `MapCache`. 874 | MapCache.prototype.clear = mapCacheClear; 875 | MapCache.prototype['delete'] = mapCacheDelete; 876 | MapCache.prototype.get = mapCacheGet; 877 | MapCache.prototype.has = mapCacheHas; 878 | MapCache.prototype.set = mapCacheSet; 879 | 880 | /*------------------------------------------------------------------------*/ 881 | 882 | /** 883 | * Creates a stack cache object to store key-value pairs. 884 | * 885 | * @private 886 | * @constructor 887 | * @param {Array} [entries] The key-value pairs to cache. 888 | */ 889 | function Stack(entries) { 890 | this.__data__ = new ListCache(entries); 891 | } 892 | 893 | /** 894 | * Removes all key-value entries from the stack. 895 | * 896 | * @private 897 | * @name clear 898 | * @memberOf Stack 899 | */ 900 | function stackClear() { 901 | this.__data__ = new ListCache; 902 | } 903 | 904 | /** 905 | * Removes `key` and its value from the stack. 906 | * 907 | * @private 908 | * @name delete 909 | * @memberOf Stack 910 | * @param {string} key The key of the value to remove. 911 | * @returns {boolean} Returns `true` if the entry was removed, else `false`. 912 | */ 913 | function stackDelete(key) { 914 | return this.__data__['delete'](key); 915 | } 916 | 917 | /** 918 | * Gets the stack value for `key`. 919 | * 920 | * @private 921 | * @name get 922 | * @memberOf Stack 923 | * @param {string} key The key of the value to get. 924 | * @returns {*} Returns the entry value. 925 | */ 926 | function stackGet(key) { 927 | return this.__data__.get(key); 928 | } 929 | 930 | /** 931 | * Checks if a stack value for `key` exists. 932 | * 933 | * @private 934 | * @name has 935 | * @memberOf Stack 936 | * @param {string} key The key of the entry to check. 937 | * @returns {boolean} Returns `true` if an entry for `key` exists, else `false`. 938 | */ 939 | function stackHas(key) { 940 | return this.__data__.has(key); 941 | } 942 | 943 | /** 944 | * Sets the stack `key` to `value`. 945 | * 946 | * @private 947 | * @name set 948 | * @memberOf Stack 949 | * @param {string} key The key of the value to set. 950 | * @param {*} value The value to set. 951 | * @returns {Object} Returns the stack cache instance. 952 | */ 953 | function stackSet(key, value) { 954 | var cache = this.__data__; 955 | if (cache instanceof ListCache && cache.__data__.length == LARGE_ARRAY_SIZE) { 956 | cache = this.__data__ = new MapCache(cache.__data__); 957 | } 958 | cache.set(key, value); 959 | return this; 960 | } 961 | 962 | // Add methods to `Stack`. 963 | Stack.prototype.clear = stackClear; 964 | Stack.prototype['delete'] = stackDelete; 965 | Stack.prototype.get = stackGet; 966 | Stack.prototype.has = stackHas; 967 | Stack.prototype.set = stackSet; 968 | 969 | /*------------------------------------------------------------------------*/ 970 | 971 | /** 972 | * This function is like `assignValue` except that it doesn't assign 973 | * `undefined` values. 974 | * 975 | * @private 976 | * @param {Object} object The object to modify. 977 | * @param {string} key The key of the property to assign. 978 | * @param {*} value The value to assign. 979 | */ 980 | function assignMergeValue(object, key, value) { 981 | if ((value !== undefined && !eq(object[key], value)) || 982 | (typeof key == 'number' && value === undefined && !(key in object))) { 983 | object[key] = value; 984 | } 985 | } 986 | 987 | /** 988 | * Assigns `value` to `key` of `object` if the existing value is not equivalent 989 | * using [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero) 990 | * for equality comparisons. 991 | * 992 | * @private 993 | * @param {Object} object The object to modify. 994 | * @param {string} key The key of the property to assign. 995 | * @param {*} value The value to assign. 996 | */ 997 | function assignValue(object, key, value) { 998 | var objValue = object[key]; 999 | if (!(hasOwnProperty.call(object, key) && eq(objValue, value)) || 1000 | (value === undefined && !(key in object))) { 1001 | object[key] = value; 1002 | } 1003 | } 1004 | 1005 | /** 1006 | * Gets the index at which the `key` is found in `array` of key-value pairs. 1007 | * 1008 | * @private 1009 | * @param {Array} array The array to search. 1010 | * @param {*} key The key to search for. 1011 | * @returns {number} Returns the index of the matched value, else `-1`. 1012 | */ 1013 | function assocIndexOf(array, key) { 1014 | var length = array.length; 1015 | while (length--) { 1016 | if (eq(array[length][0], key)) { 1017 | return length; 1018 | } 1019 | } 1020 | return -1; 1021 | } 1022 | 1023 | /** 1024 | * The base implementation of `_.assign` without support for multiple sources 1025 | * or `customizer` functions. 1026 | * 1027 | * @private 1028 | * @param {Object} object The destination object. 1029 | * @param {Object} source The source object. 1030 | * @returns {Object} Returns `object`. 1031 | */ 1032 | function baseAssign(object, source) { 1033 | return object && copyObject(source, keys(source), object); 1034 | } 1035 | 1036 | /** 1037 | * The base implementation of `_.clone` and `_.cloneDeep` which tracks 1038 | * traversed objects. 1039 | * 1040 | * @private 1041 | * @param {*} value The value to clone. 1042 | * @param {boolean} [isDeep] Specify a deep clone. 1043 | * @param {boolean} [isFull] Specify a clone including symbols. 1044 | * @param {Function} [customizer] The function to customize cloning. 1045 | * @param {string} [key] The key of `value`. 1046 | * @param {Object} [object] The parent object of `value`. 1047 | * @param {Object} [stack] Tracks traversed objects and their clone counterparts. 1048 | * @returns {*} Returns the cloned value. 1049 | */ 1050 | function baseClone(value, isDeep, isFull, customizer, key, object, stack) { 1051 | var result; 1052 | if (customizer) { 1053 | result = object ? customizer(value, key, object, stack) : customizer(value); 1054 | } 1055 | if (result !== undefined) { 1056 | return result; 1057 | } 1058 | if (!isObject(value)) { 1059 | return value; 1060 | } 1061 | var isArr = isArray(value); 1062 | if (isArr) { 1063 | result = initCloneArray(value); 1064 | if (!isDeep) { 1065 | return copyArray(value, result); 1066 | } 1067 | } else { 1068 | var tag = getTag(value), 1069 | isFunc = tag == funcTag || tag == genTag; 1070 | 1071 | if (isBuffer(value)) { 1072 | return cloneBuffer(value, isDeep); 1073 | } 1074 | if (tag == objectTag || tag == argsTag || (isFunc && !object)) { 1075 | if (isHostObject(value)) { 1076 | return object ? value : {}; 1077 | } 1078 | result = initCloneObject(isFunc ? {} : value); 1079 | if (!isDeep) { 1080 | return copySymbols(value, baseAssign(result, value)); 1081 | } 1082 | } else { 1083 | if (!cloneableTags[tag]) { 1084 | return object ? value : {}; 1085 | } 1086 | result = initCloneByTag(value, tag, baseClone, isDeep); 1087 | } 1088 | } 1089 | // Check for circular references and return its corresponding clone. 1090 | stack || (stack = new Stack); 1091 | var stacked = stack.get(value); 1092 | if (stacked) { 1093 | return stacked; 1094 | } 1095 | stack.set(value, result); 1096 | 1097 | if (!isArr) { 1098 | var props = isFull ? getAllKeys(value) : keys(value); 1099 | } 1100 | // Recursively populate clone (susceptible to call stack limits). 1101 | arrayEach(props || value, function(subValue, key) { 1102 | if (props) { 1103 | key = subValue; 1104 | subValue = value[key]; 1105 | } 1106 | assignValue(result, key, baseClone(subValue, isDeep, isFull, customizer, key, value, stack)); 1107 | }); 1108 | return result; 1109 | } 1110 | 1111 | /** 1112 | * The base implementation of `_.create` without support for assigning 1113 | * properties to the created object. 1114 | * 1115 | * @private 1116 | * @param {Object} prototype The object to inherit from. 1117 | * @returns {Object} Returns the new object. 1118 | */ 1119 | function baseCreate(proto) { 1120 | return isObject(proto) ? objectCreate(proto) : {}; 1121 | } 1122 | 1123 | /** 1124 | * The base implementation of `getAllKeys` and `getAllKeysIn` which uses 1125 | * `keysFunc` and `symbolsFunc` to get the enumerable property names and 1126 | * symbols of `object`. 1127 | * 1128 | * @private 1129 | * @param {Object} object The object to query. 1130 | * @param {Function} keysFunc The function to get the keys of `object`. 1131 | * @param {Function} symbolsFunc The function to get the symbols of `object`. 1132 | * @returns {Array} Returns the array of property names and symbols. 1133 | */ 1134 | function baseGetAllKeys(object, keysFunc, symbolsFunc) { 1135 | var result = keysFunc(object); 1136 | return isArray(object) ? result : arrayPush(result, symbolsFunc(object)); 1137 | } 1138 | 1139 | /** 1140 | * The base implementation of `_.has` without support for deep paths. 1141 | * 1142 | * @private 1143 | * @param {Object} object The object to query. 1144 | * @param {Array|string} key The key to check. 1145 | * @returns {boolean} Returns `true` if `key` exists, else `false`. 1146 | */ 1147 | function baseHas(object, key) { 1148 | // Avoid a bug in IE 10-11 where objects with a [[Prototype]] of `null`, 1149 | // that are composed entirely of index properties, return `false` for 1150 | // `hasOwnProperty` checks of them. 1151 | return hasOwnProperty.call(object, key) || 1152 | (typeof object == 'object' && key in object && getPrototype(object) === null); 1153 | } 1154 | 1155 | /** 1156 | * The base implementation of `_.keys` which doesn't skip the constructor 1157 | * property of prototypes or treat sparse arrays as dense. 1158 | * 1159 | * @private 1160 | * @param {Object} object The object to query. 1161 | * @returns {Array} Returns the array of property names. 1162 | */ 1163 | function baseKeys(object) { 1164 | return nativeKeys(Object(object)); 1165 | } 1166 | 1167 | /** 1168 | * The base implementation of `_.keysIn` which doesn't skip the constructor 1169 | * property of prototypes or treat sparse arrays as dense. 1170 | * 1171 | * @private 1172 | * @param {Object} object The object to query. 1173 | * @returns {Array} Returns the array of property names. 1174 | */ 1175 | function baseKeysIn(object) { 1176 | object = object == null ? object : Object(object); 1177 | 1178 | var result = []; 1179 | for (var key in object) { 1180 | result.push(key); 1181 | } 1182 | return result; 1183 | } 1184 | 1185 | // Fallback for IE < 9 with es6-shim. 1186 | if (enumerate && !propertyIsEnumerable.call({ 'valueOf': 1 }, 'valueOf')) { 1187 | baseKeysIn = function(object) { 1188 | return iteratorToArray(enumerate(object)); 1189 | }; 1190 | } 1191 | 1192 | /** 1193 | * The base implementation of `_.merge` without support for multiple sources. 1194 | * 1195 | * @private 1196 | * @param {Object} object The destination object. 1197 | * @param {Object} source The source object. 1198 | * @param {number} srcIndex The index of `source`. 1199 | * @param {Function} [customizer] The function to customize merged values. 1200 | * @param {Object} [stack] Tracks traversed source values and their merged 1201 | * counterparts. 1202 | */ 1203 | function baseMerge(object, source, srcIndex, customizer, stack) { 1204 | if (object === source) { 1205 | return; 1206 | } 1207 | if (!(isArray(source) || isTypedArray(source))) { 1208 | var props = keysIn(source); 1209 | } 1210 | arrayEach(props || source, function(srcValue, key) { 1211 | if (props) { 1212 | key = srcValue; 1213 | srcValue = source[key]; 1214 | } 1215 | if (isObject(srcValue)) { 1216 | stack || (stack = new Stack); 1217 | baseMergeDeep(object, source, key, srcIndex, baseMerge, customizer, stack); 1218 | } 1219 | else { 1220 | var newValue = customizer 1221 | ? customizer(object[key], srcValue, (key + ''), object, source, stack) 1222 | : undefined; 1223 | 1224 | if (newValue === undefined) { 1225 | newValue = srcValue; 1226 | } 1227 | assignMergeValue(object, key, newValue); 1228 | } 1229 | }); 1230 | } 1231 | 1232 | /** 1233 | * A specialized version of `baseMerge` for arrays and objects which performs 1234 | * deep merges and tracks traversed objects enabling objects with circular 1235 | * references to be merged. 1236 | * 1237 | * @private 1238 | * @param {Object} object The destination object. 1239 | * @param {Object} source The source object. 1240 | * @param {string} key The key of the value to merge. 1241 | * @param {number} srcIndex The index of `source`. 1242 | * @param {Function} mergeFunc The function to merge values. 1243 | * @param {Function} [customizer] The function to customize assigned values. 1244 | * @param {Object} [stack] Tracks traversed source values and their merged 1245 | * counterparts. 1246 | */ 1247 | function baseMergeDeep(object, source, key, srcIndex, mergeFunc, customizer, stack) { 1248 | var objValue = object[key], 1249 | srcValue = source[key], 1250 | stacked = stack.get(srcValue); 1251 | 1252 | if (stacked) { 1253 | assignMergeValue(object, key, stacked); 1254 | return; 1255 | } 1256 | var newValue = customizer 1257 | ? customizer(objValue, srcValue, (key + ''), object, source, stack) 1258 | : undefined; 1259 | 1260 | var isCommon = newValue === undefined; 1261 | 1262 | if (isCommon) { 1263 | newValue = srcValue; 1264 | if (isArray(srcValue) || isTypedArray(srcValue)) { 1265 | if (isArray(objValue)) { 1266 | newValue = objValue; 1267 | } 1268 | else if (isArrayLikeObject(objValue)) { 1269 | newValue = copyArray(objValue); 1270 | } 1271 | else { 1272 | isCommon = false; 1273 | newValue = baseClone(srcValue, true); 1274 | } 1275 | } 1276 | else if (isPlainObject(srcValue) || isArguments(srcValue)) { 1277 | if (isArguments(objValue)) { 1278 | newValue = toPlainObject(objValue); 1279 | } 1280 | else if (!isObject(objValue) || (srcIndex && isFunction(objValue))) { 1281 | isCommon = false; 1282 | newValue = baseClone(srcValue, true); 1283 | } 1284 | else { 1285 | newValue = objValue; 1286 | } 1287 | } 1288 | else { 1289 | isCommon = false; 1290 | } 1291 | } 1292 | stack.set(srcValue, newValue); 1293 | 1294 | if (isCommon) { 1295 | // Recursively merge objects and arrays (susceptible to call stack limits). 1296 | mergeFunc(newValue, srcValue, srcIndex, customizer, stack); 1297 | } 1298 | stack['delete'](srcValue); 1299 | assignMergeValue(object, key, newValue); 1300 | } 1301 | 1302 | /** 1303 | * The base implementation of `_.property` without support for deep paths. 1304 | * 1305 | * @private 1306 | * @param {string} key The key of the property to get. 1307 | * @returns {Function} Returns the new accessor function. 1308 | */ 1309 | function baseProperty(key) { 1310 | return function(object) { 1311 | return object == null ? undefined : object[key]; 1312 | }; 1313 | } 1314 | 1315 | /** 1316 | * Creates a clone of `buffer`. 1317 | * 1318 | * @private 1319 | * @param {Buffer} buffer The buffer to clone. 1320 | * @param {boolean} [isDeep] Specify a deep clone. 1321 | * @returns {Buffer} Returns the cloned buffer. 1322 | */ 1323 | function cloneBuffer(buffer, isDeep) { 1324 | if (isDeep) { 1325 | return buffer.slice(); 1326 | } 1327 | var result = new buffer.constructor(buffer.length); 1328 | buffer.copy(result); 1329 | return result; 1330 | } 1331 | 1332 | /** 1333 | * Creates a clone of `arrayBuffer`. 1334 | * 1335 | * @private 1336 | * @param {ArrayBuffer} arrayBuffer The array buffer to clone. 1337 | * @returns {ArrayBuffer} Returns the cloned array buffer. 1338 | */ 1339 | function cloneArrayBuffer(arrayBuffer) { 1340 | var result = new arrayBuffer.constructor(arrayBuffer.byteLength); 1341 | new Uint8Array(result).set(new Uint8Array(arrayBuffer)); 1342 | return result; 1343 | } 1344 | 1345 | /** 1346 | * Creates a clone of `dataView`. 1347 | * 1348 | * @private 1349 | * @param {Object} dataView The data view to clone. 1350 | * @param {boolean} [isDeep] Specify a deep clone. 1351 | * @returns {Object} Returns the cloned data view. 1352 | */ 1353 | function cloneDataView(dataView, isDeep) { 1354 | var buffer = isDeep ? cloneArrayBuffer(dataView.buffer) : dataView.buffer; 1355 | return new dataView.constructor(buffer, dataView.byteOffset, dataView.byteLength); 1356 | } 1357 | 1358 | /** 1359 | * Creates a clone of `map`. 1360 | * 1361 | * @private 1362 | * @param {Object} map The map to clone. 1363 | * @param {Function} cloneFunc The function to clone values. 1364 | * @param {boolean} [isDeep] Specify a deep clone. 1365 | * @returns {Object} Returns the cloned map. 1366 | */ 1367 | function cloneMap(map, isDeep, cloneFunc) { 1368 | var array = isDeep ? cloneFunc(mapToArray(map), true) : mapToArray(map); 1369 | return arrayReduce(array, addMapEntry, new map.constructor); 1370 | } 1371 | 1372 | /** 1373 | * Creates a clone of `regexp`. 1374 | * 1375 | * @private 1376 | * @param {Object} regexp The regexp to clone. 1377 | * @returns {Object} Returns the cloned regexp. 1378 | */ 1379 | function cloneRegExp(regexp) { 1380 | var result = new regexp.constructor(regexp.source, reFlags.exec(regexp)); 1381 | result.lastIndex = regexp.lastIndex; 1382 | return result; 1383 | } 1384 | 1385 | /** 1386 | * Creates a clone of `set`. 1387 | * 1388 | * @private 1389 | * @param {Object} set The set to clone. 1390 | * @param {Function} cloneFunc The function to clone values. 1391 | * @param {boolean} [isDeep] Specify a deep clone. 1392 | * @returns {Object} Returns the cloned set. 1393 | */ 1394 | function cloneSet(set, isDeep, cloneFunc) { 1395 | var array = isDeep ? cloneFunc(setToArray(set), true) : setToArray(set); 1396 | return arrayReduce(array, addSetEntry, new set.constructor); 1397 | } 1398 | 1399 | /** 1400 | * Creates a clone of the `symbol` object. 1401 | * 1402 | * @private 1403 | * @param {Object} symbol The symbol object to clone. 1404 | * @returns {Object} Returns the cloned symbol object. 1405 | */ 1406 | function cloneSymbol(symbol) { 1407 | return symbolValueOf ? Object(symbolValueOf.call(symbol)) : {}; 1408 | } 1409 | 1410 | /** 1411 | * Creates a clone of `typedArray`. 1412 | * 1413 | * @private 1414 | * @param {Object} typedArray The typed array to clone. 1415 | * @param {boolean} [isDeep] Specify a deep clone. 1416 | * @returns {Object} Returns the cloned typed array. 1417 | */ 1418 | function cloneTypedArray(typedArray, isDeep) { 1419 | var buffer = isDeep ? cloneArrayBuffer(typedArray.buffer) : typedArray.buffer; 1420 | return new typedArray.constructor(buffer, typedArray.byteOffset, typedArray.length); 1421 | } 1422 | 1423 | /** 1424 | * Copies the values of `source` to `array`. 1425 | * 1426 | * @private 1427 | * @param {Array} source The array to copy values from. 1428 | * @param {Array} [array=[]] The array to copy values to. 1429 | * @returns {Array} Returns `array`. 1430 | */ 1431 | function copyArray(source, array) { 1432 | var index = -1, 1433 | length = source.length; 1434 | 1435 | array || (array = Array(length)); 1436 | while (++index < length) { 1437 | array[index] = source[index]; 1438 | } 1439 | return array; 1440 | } 1441 | 1442 | /** 1443 | * Copies properties of `source` to `object`. 1444 | * 1445 | * @private 1446 | * @param {Object} source The object to copy properties from. 1447 | * @param {Array} props The property identifiers to copy. 1448 | * @param {Object} [object={}] The object to copy properties to. 1449 | * @param {Function} [customizer] The function to customize copied values. 1450 | * @returns {Object} Returns `object`. 1451 | */ 1452 | function copyObject(source, props, object, customizer) { 1453 | object || (object = {}); 1454 | 1455 | var index = -1, 1456 | length = props.length; 1457 | 1458 | while (++index < length) { 1459 | var key = props[index]; 1460 | 1461 | var newValue = customizer 1462 | ? customizer(object[key], source[key], key, object, source) 1463 | : source[key]; 1464 | 1465 | assignValue(object, key, newValue); 1466 | } 1467 | return object; 1468 | } 1469 | 1470 | /** 1471 | * Copies own symbol properties of `source` to `object`. 1472 | * 1473 | * @private 1474 | * @param {Object} source The object to copy symbols from. 1475 | * @param {Object} [object={}] The object to copy symbols to. 1476 | * @returns {Object} Returns `object`. 1477 | */ 1478 | function copySymbols(source, object) { 1479 | return copyObject(source, getSymbols(source), object); 1480 | } 1481 | 1482 | /** 1483 | * Creates a function like `_.assign`. 1484 | * 1485 | * @private 1486 | * @param {Function} assigner The function to assign values. 1487 | * @returns {Function} Returns the new assigner function. 1488 | */ 1489 | function createAssigner(assigner) { 1490 | return rest(function(object, sources) { 1491 | var index = -1, 1492 | length = sources.length, 1493 | customizer = length > 1 ? sources[length - 1] : undefined, 1494 | guard = length > 2 ? sources[2] : undefined; 1495 | 1496 | customizer = (assigner.length > 3 && typeof customizer == 'function') 1497 | ? (length--, customizer) 1498 | : undefined; 1499 | 1500 | if (guard && isIterateeCall(sources[0], sources[1], guard)) { 1501 | customizer = length < 3 ? undefined : customizer; 1502 | length = 1; 1503 | } 1504 | object = Object(object); 1505 | while (++index < length) { 1506 | var source = sources[index]; 1507 | if (source) { 1508 | assigner(object, source, index, customizer); 1509 | } 1510 | } 1511 | return object; 1512 | }); 1513 | } 1514 | 1515 | /** 1516 | * Creates an array of own enumerable property names and symbols of `object`. 1517 | * 1518 | * @private 1519 | * @param {Object} object The object to query. 1520 | * @returns {Array} Returns the array of property names and symbols. 1521 | */ 1522 | function getAllKeys(object) { 1523 | return baseGetAllKeys(object, keys, getSymbols); 1524 | } 1525 | 1526 | /** 1527 | * Gets the "length" property value of `object`. 1528 | * 1529 | * **Note:** This function is used to avoid a 1530 | * [JIT bug](https://bugs.webkit.org/show_bug.cgi?id=142792) that affects 1531 | * Safari on at least iOS 8.1-8.3 ARM64. 1532 | * 1533 | * @private 1534 | * @param {Object} object The object to query. 1535 | * @returns {*} Returns the "length" value. 1536 | */ 1537 | var getLength = baseProperty('length'); 1538 | 1539 | /** 1540 | * Gets the data for `map`. 1541 | * 1542 | * @private 1543 | * @param {Object} map The map to query. 1544 | * @param {string} key The reference key. 1545 | * @returns {*} Returns the map data. 1546 | */ 1547 | function getMapData(map, key) { 1548 | var data = map.__data__; 1549 | return isKeyable(key) 1550 | ? data[typeof key == 'string' ? 'string' : 'hash'] 1551 | : data.map; 1552 | } 1553 | 1554 | /** 1555 | * Gets the native function at `key` of `object`. 1556 | * 1557 | * @private 1558 | * @param {Object} object The object to query. 1559 | * @param {string} key The key of the method to get. 1560 | * @returns {*} Returns the function if it's native, else `undefined`. 1561 | */ 1562 | function getNative(object, key) { 1563 | var value = object[key]; 1564 | return isNative(value) ? value : undefined; 1565 | } 1566 | 1567 | /** 1568 | * Gets the `[[Prototype]]` of `value`. 1569 | * 1570 | * @private 1571 | * @param {*} value The value to query. 1572 | * @returns {null|Object} Returns the `[[Prototype]]`. 1573 | */ 1574 | function getPrototype(value) { 1575 | return nativeGetPrototype(Object(value)); 1576 | } 1577 | 1578 | /** 1579 | * Creates an array of the own enumerable symbol properties of `object`. 1580 | * 1581 | * @private 1582 | * @param {Object} object The object to query. 1583 | * @returns {Array} Returns the array of symbols. 1584 | */ 1585 | function getSymbols(object) { 1586 | // Coerce `object` to an object to avoid non-object errors in V8. 1587 | // See https://bugs.chromium.org/p/v8/issues/detail?id=3443 for more details. 1588 | return getOwnPropertySymbols(Object(object)); 1589 | } 1590 | 1591 | // Fallback for IE < 11. 1592 | if (!getOwnPropertySymbols) { 1593 | getSymbols = function() { 1594 | return []; 1595 | }; 1596 | } 1597 | 1598 | /** 1599 | * Gets the `toStringTag` of `value`. 1600 | * 1601 | * @private 1602 | * @param {*} value The value to query. 1603 | * @returns {string} Returns the `toStringTag`. 1604 | */ 1605 | function getTag(value) { 1606 | return objectToString.call(value); 1607 | } 1608 | 1609 | // Fallback for data views, maps, sets, and weak maps in IE 11, 1610 | // for data views in Edge, and promises in Node.js. 1611 | if ((DataView && getTag(new DataView(new ArrayBuffer(1))) != dataViewTag) || 1612 | (Map && getTag(new Map) != mapTag) || 1613 | (Promise && getTag(Promise.resolve()) != promiseTag) || 1614 | (Set && getTag(new Set) != setTag) || 1615 | (WeakMap && getTag(new WeakMap) != weakMapTag)) { 1616 | getTag = function(value) { 1617 | var result = objectToString.call(value), 1618 | Ctor = result == objectTag ? value.constructor : undefined, 1619 | ctorString = Ctor ? toSource(Ctor) : undefined; 1620 | 1621 | if (ctorString) { 1622 | switch (ctorString) { 1623 | case dataViewCtorString: return dataViewTag; 1624 | case mapCtorString: return mapTag; 1625 | case promiseCtorString: return promiseTag; 1626 | case setCtorString: return setTag; 1627 | case weakMapCtorString: return weakMapTag; 1628 | } 1629 | } 1630 | return result; 1631 | }; 1632 | } 1633 | 1634 | /** 1635 | * Initializes an array clone. 1636 | * 1637 | * @private 1638 | * @param {Array} array The array to clone. 1639 | * @returns {Array} Returns the initialized clone. 1640 | */ 1641 | function initCloneArray(array) { 1642 | var length = array.length, 1643 | result = array.constructor(length); 1644 | 1645 | // Add properties assigned by `RegExp#exec`. 1646 | if (length && typeof array[0] == 'string' && hasOwnProperty.call(array, 'index')) { 1647 | result.index = array.index; 1648 | result.input = array.input; 1649 | } 1650 | return result; 1651 | } 1652 | 1653 | /** 1654 | * Initializes an object clone. 1655 | * 1656 | * @private 1657 | * @param {Object} object The object to clone. 1658 | * @returns {Object} Returns the initialized clone. 1659 | */ 1660 | function initCloneObject(object) { 1661 | return (typeof object.constructor == 'function' && !isPrototype(object)) 1662 | ? baseCreate(getPrototype(object)) 1663 | : {}; 1664 | } 1665 | 1666 | /** 1667 | * Initializes an object clone based on its `toStringTag`. 1668 | * 1669 | * **Note:** This function only supports cloning values with tags of 1670 | * `Boolean`, `Date`, `Error`, `Number`, `RegExp`, or `String`. 1671 | * 1672 | * @private 1673 | * @param {Object} object The object to clone. 1674 | * @param {string} tag The `toStringTag` of the object to clone. 1675 | * @param {Function} cloneFunc The function to clone values. 1676 | * @param {boolean} [isDeep] Specify a deep clone. 1677 | * @returns {Object} Returns the initialized clone. 1678 | */ 1679 | function initCloneByTag(object, tag, cloneFunc, isDeep) { 1680 | var Ctor = object.constructor; 1681 | switch (tag) { 1682 | case arrayBufferTag: 1683 | return cloneArrayBuffer(object); 1684 | 1685 | case boolTag: 1686 | case dateTag: 1687 | return new Ctor(+object); 1688 | 1689 | case dataViewTag: 1690 | return cloneDataView(object, isDeep); 1691 | 1692 | case float32Tag: case float64Tag: 1693 | case int8Tag: case int16Tag: case int32Tag: 1694 | case uint8Tag: case uint8ClampedTag: case uint16Tag: case uint32Tag: 1695 | return cloneTypedArray(object, isDeep); 1696 | 1697 | case mapTag: 1698 | return cloneMap(object, isDeep, cloneFunc); 1699 | 1700 | case numberTag: 1701 | case stringTag: 1702 | return new Ctor(object); 1703 | 1704 | case regexpTag: 1705 | return cloneRegExp(object); 1706 | 1707 | case setTag: 1708 | return cloneSet(object, isDeep, cloneFunc); 1709 | 1710 | case symbolTag: 1711 | return cloneSymbol(object); 1712 | } 1713 | } 1714 | 1715 | /** 1716 | * Creates an array of index keys for `object` values of arrays, 1717 | * `arguments` objects, and strings, otherwise `null` is returned. 1718 | * 1719 | * @private 1720 | * @param {Object} object The object to query. 1721 | * @returns {Array|null} Returns index keys, else `null`. 1722 | */ 1723 | function indexKeys(object) { 1724 | var length = object ? object.length : undefined; 1725 | if (isLength(length) && 1726 | (isArray(object) || isString(object) || isArguments(object))) { 1727 | return baseTimes(length, String); 1728 | } 1729 | return null; 1730 | } 1731 | 1732 | /** 1733 | * Checks if `value` is a valid array-like index. 1734 | * 1735 | * @private 1736 | * @param {*} value The value to check. 1737 | * @param {number} [length=MAX_SAFE_INTEGER] The upper bounds of a valid index. 1738 | * @returns {boolean} Returns `true` if `value` is a valid index, else `false`. 1739 | */ 1740 | function isIndex(value, length) { 1741 | length = length == null ? MAX_SAFE_INTEGER : length; 1742 | return !!length && 1743 | (typeof value == 'number' || reIsUint.test(value)) && 1744 | (value > -1 && value % 1 == 0 && value < length); 1745 | } 1746 | 1747 | /** 1748 | * Checks if the given arguments are from an iteratee call. 1749 | * 1750 | * @private 1751 | * @param {*} value The potential iteratee value argument. 1752 | * @param {*} index The potential iteratee index or key argument. 1753 | * @param {*} object The potential iteratee object argument. 1754 | * @returns {boolean} Returns `true` if the arguments are from an iteratee call, 1755 | * else `false`. 1756 | */ 1757 | function isIterateeCall(value, index, object) { 1758 | if (!isObject(object)) { 1759 | return false; 1760 | } 1761 | var type = typeof index; 1762 | if (type == 'number' 1763 | ? (isArrayLike(object) && isIndex(index, object.length)) 1764 | : (type == 'string' && index in object) 1765 | ) { 1766 | return eq(object[index], value); 1767 | } 1768 | return false; 1769 | } 1770 | 1771 | /** 1772 | * Checks if `value` is suitable for use as unique object key. 1773 | * 1774 | * @private 1775 | * @param {*} value The value to check. 1776 | * @returns {boolean} Returns `true` if `value` is suitable, else `false`. 1777 | */ 1778 | function isKeyable(value) { 1779 | var type = typeof value; 1780 | return (type == 'string' || type == 'number' || type == 'symbol' || type == 'boolean') 1781 | ? (value !== '__proto__') 1782 | : (value === null); 1783 | } 1784 | 1785 | /** 1786 | * Checks if `value` is likely a prototype object. 1787 | * 1788 | * @private 1789 | * @param {*} value The value to check. 1790 | * @returns {boolean} Returns `true` if `value` is a prototype, else `false`. 1791 | */ 1792 | function isPrototype(value) { 1793 | var Ctor = value && value.constructor, 1794 | proto = (typeof Ctor == 'function' && Ctor.prototype) || objectProto; 1795 | 1796 | return value === proto; 1797 | } 1798 | 1799 | /** 1800 | * Converts `func` to its source code. 1801 | * 1802 | * @private 1803 | * @param {Function} func The function to process. 1804 | * @returns {string} Returns the source code. 1805 | */ 1806 | function toSource(func) { 1807 | if (func != null) { 1808 | try { 1809 | return funcToString.call(func); 1810 | } catch (e) {} 1811 | try { 1812 | return (func + ''); 1813 | } catch (e) {} 1814 | } 1815 | return ''; 1816 | } 1817 | 1818 | /*------------------------------------------------------------------------*/ 1819 | 1820 | /** 1821 | * Creates a function that invokes `func` with the `this` binding of the 1822 | * created function and arguments from `start` and beyond provided as 1823 | * an array. 1824 | * 1825 | * **Note:** This method is based on the 1826 | * [rest parameter](https://mdn.io/rest_parameters). 1827 | * 1828 | * @static 1829 | * @memberOf _ 1830 | * @since 4.0.0 1831 | * @category Function 1832 | * @param {Function} func The function to apply a rest parameter to. 1833 | * @param {number} [start=func.length-1] The start position of the rest parameter. 1834 | * @returns {Function} Returns the new function. 1835 | * @example 1836 | * 1837 | * var say = _.rest(function(what, names) { 1838 | * return what + ' ' + _.initial(names).join(', ') + 1839 | * (_.size(names) > 1 ? ', & ' : '') + _.last(names); 1840 | * }); 1841 | * 1842 | * say('hello', 'fred', 'barney', 'pebbles'); 1843 | * // => 'hello fred, barney, & pebbles' 1844 | */ 1845 | function rest(func, start) { 1846 | if (typeof func != 'function') { 1847 | throw new TypeError(FUNC_ERROR_TEXT); 1848 | } 1849 | start = nativeMax(start === undefined ? (func.length - 1) : toInteger(start), 0); 1850 | return function() { 1851 | var args = arguments, 1852 | index = -1, 1853 | length = nativeMax(args.length - start, 0), 1854 | array = Array(length); 1855 | 1856 | while (++index < length) { 1857 | array[index] = args[start + index]; 1858 | } 1859 | switch (start) { 1860 | case 0: return func.call(this, array); 1861 | case 1: return func.call(this, args[0], array); 1862 | case 2: return func.call(this, args[0], args[1], array); 1863 | } 1864 | var otherArgs = Array(start + 1); 1865 | index = -1; 1866 | while (++index < start) { 1867 | otherArgs[index] = args[index]; 1868 | } 1869 | otherArgs[start] = array; 1870 | return apply(func, this, otherArgs); 1871 | }; 1872 | } 1873 | 1874 | /*------------------------------------------------------------------------*/ 1875 | 1876 | /** 1877 | * This method is like `_.clone` except that it recursively clones `value`. 1878 | * 1879 | * @static 1880 | * @memberOf _ 1881 | * @since 1.0.0 1882 | * @category Lang 1883 | * @param {*} value The value to recursively clone. 1884 | * @returns {*} Returns the deep cloned value. 1885 | * @see _.clone 1886 | * @example 1887 | * 1888 | * var objects = [{ 'a': 1 }, { 'b': 2 }]; 1889 | * 1890 | * var deep = _.cloneDeep(objects); 1891 | * console.log(deep[0] === objects[0]); 1892 | * // => false 1893 | */ 1894 | function cloneDeep(value) { 1895 | return baseClone(value, true, true); 1896 | } 1897 | 1898 | /** 1899 | * Performs a 1900 | * [`SameValueZero`](http://ecma-international.org/ecma-262/6.0/#sec-samevaluezero) 1901 | * comparison between two values to determine if they are equivalent. 1902 | * 1903 | * @static 1904 | * @memberOf _ 1905 | * @since 4.0.0 1906 | * @category Lang 1907 | * @param {*} value The value to compare. 1908 | * @param {*} other The other value to compare. 1909 | * @returns {boolean} Returns `true` if the values are equivalent, else `false`. 1910 | * @example 1911 | * 1912 | * var object = { 'user': 'fred' }; 1913 | * var other = { 'user': 'fred' }; 1914 | * 1915 | * _.eq(object, object); 1916 | * // => true 1917 | * 1918 | * _.eq(object, other); 1919 | * // => false 1920 | * 1921 | * _.eq('a', 'a'); 1922 | * // => true 1923 | * 1924 | * _.eq('a', Object('a')); 1925 | * // => false 1926 | * 1927 | * _.eq(NaN, NaN); 1928 | * // => true 1929 | */ 1930 | function eq(value, other) { 1931 | return value === other || (value !== value && other !== other); 1932 | } 1933 | 1934 | /** 1935 | * Checks if `value` is likely an `arguments` object. 1936 | * 1937 | * @static 1938 | * @memberOf _ 1939 | * @since 0.1.0 1940 | * @category Lang 1941 | * @param {*} value The value to check. 1942 | * @returns {boolean} Returns `true` if `value` is correctly classified, 1943 | * else `false`. 1944 | * @example 1945 | * 1946 | * _.isArguments(function() { return arguments; }()); 1947 | * // => true 1948 | * 1949 | * _.isArguments([1, 2, 3]); 1950 | * // => false 1951 | */ 1952 | function isArguments(value) { 1953 | // Safari 8.1 incorrectly makes `arguments.callee` enumerable in strict mode. 1954 | return isArrayLikeObject(value) && hasOwnProperty.call(value, 'callee') && 1955 | (!propertyIsEnumerable.call(value, 'callee') || objectToString.call(value) == argsTag); 1956 | } 1957 | 1958 | /** 1959 | * Checks if `value` is classified as an `Array` object. 1960 | * 1961 | * @static 1962 | * @memberOf _ 1963 | * @since 0.1.0 1964 | * @type {Function} 1965 | * @category Lang 1966 | * @param {*} value The value to check. 1967 | * @returns {boolean} Returns `true` if `value` is correctly classified, 1968 | * else `false`. 1969 | * @example 1970 | * 1971 | * _.isArray([1, 2, 3]); 1972 | * // => true 1973 | * 1974 | * _.isArray(document.body.children); 1975 | * // => false 1976 | * 1977 | * _.isArray('abc'); 1978 | * // => false 1979 | * 1980 | * _.isArray(_.noop); 1981 | * // => false 1982 | */ 1983 | var isArray = Array.isArray; 1984 | 1985 | /** 1986 | * Checks if `value` is array-like. A value is considered array-like if it's 1987 | * not a function and has a `value.length` that's an integer greater than or 1988 | * equal to `0` and less than or equal to `Number.MAX_SAFE_INTEGER`. 1989 | * 1990 | * @static 1991 | * @memberOf _ 1992 | * @since 4.0.0 1993 | * @category Lang 1994 | * @param {*} value The value to check. 1995 | * @returns {boolean} Returns `true` if `value` is array-like, else `false`. 1996 | * @example 1997 | * 1998 | * _.isArrayLike([1, 2, 3]); 1999 | * // => true 2000 | * 2001 | * _.isArrayLike(document.body.children); 2002 | * // => true 2003 | * 2004 | * _.isArrayLike('abc'); 2005 | * // => true 2006 | * 2007 | * _.isArrayLike(_.noop); 2008 | * // => false 2009 | */ 2010 | function isArrayLike(value) { 2011 | return value != null && isLength(getLength(value)) && !isFunction(value); 2012 | } 2013 | 2014 | /** 2015 | * This method is like `_.isArrayLike` except that it also checks if `value` 2016 | * is an object. 2017 | * 2018 | * @static 2019 | * @memberOf _ 2020 | * @since 4.0.0 2021 | * @category Lang 2022 | * @param {*} value The value to check. 2023 | * @returns {boolean} Returns `true` if `value` is an array-like object, 2024 | * else `false`. 2025 | * @example 2026 | * 2027 | * _.isArrayLikeObject([1, 2, 3]); 2028 | * // => true 2029 | * 2030 | * _.isArrayLikeObject(document.body.children); 2031 | * // => true 2032 | * 2033 | * _.isArrayLikeObject('abc'); 2034 | * // => false 2035 | * 2036 | * _.isArrayLikeObject(_.noop); 2037 | * // => false 2038 | */ 2039 | function isArrayLikeObject(value) { 2040 | return isObjectLike(value) && isArrayLike(value); 2041 | } 2042 | 2043 | /** 2044 | * Checks if `value` is a buffer. 2045 | * 2046 | * @static 2047 | * @memberOf _ 2048 | * @since 4.3.0 2049 | * @category Lang 2050 | * @param {*} value The value to check. 2051 | * @returns {boolean} Returns `true` if `value` is a buffer, else `false`. 2052 | * @example 2053 | * 2054 | * _.isBuffer(new Buffer(2)); 2055 | * // => true 2056 | * 2057 | * _.isBuffer(new Uint8Array(2)); 2058 | * // => false 2059 | */ 2060 | var isBuffer = !Buffer ? constant(false) : function(value) { 2061 | return value instanceof Buffer; 2062 | }; 2063 | 2064 | /** 2065 | * Checks if `value` is classified as a `Function` object. 2066 | * 2067 | * @static 2068 | * @memberOf _ 2069 | * @since 0.1.0 2070 | * @category Lang 2071 | * @param {*} value The value to check. 2072 | * @returns {boolean} Returns `true` if `value` is correctly classified, 2073 | * else `false`. 2074 | * @example 2075 | * 2076 | * _.isFunction(_); 2077 | * // => true 2078 | * 2079 | * _.isFunction(/abc/); 2080 | * // => false 2081 | */ 2082 | function isFunction(value) { 2083 | // The use of `Object#toString` avoids issues with the `typeof` operator 2084 | // in Safari 8 which returns 'object' for typed array and weak map constructors, 2085 | // and PhantomJS 1.9 which returns 'function' for `NodeList` instances. 2086 | var tag = isObject(value) ? objectToString.call(value) : ''; 2087 | return tag == funcTag || tag == genTag; 2088 | } 2089 | 2090 | /** 2091 | * Checks if `value` is a valid array-like length. 2092 | * 2093 | * **Note:** This function is loosely based on 2094 | * [`ToLength`](http://ecma-international.org/ecma-262/6.0/#sec-tolength). 2095 | * 2096 | * @static 2097 | * @memberOf _ 2098 | * @since 4.0.0 2099 | * @category Lang 2100 | * @param {*} value The value to check. 2101 | * @returns {boolean} Returns `true` if `value` is a valid length, 2102 | * else `false`. 2103 | * @example 2104 | * 2105 | * _.isLength(3); 2106 | * // => true 2107 | * 2108 | * _.isLength(Number.MIN_VALUE); 2109 | * // => false 2110 | * 2111 | * _.isLength(Infinity); 2112 | * // => false 2113 | * 2114 | * _.isLength('3'); 2115 | * // => false 2116 | */ 2117 | function isLength(value) { 2118 | return typeof value == 'number' && 2119 | value > -1 && value % 1 == 0 && value <= MAX_SAFE_INTEGER; 2120 | } 2121 | 2122 | /** 2123 | * Checks if `value` is the 2124 | * [language type](http://www.ecma-international.org/ecma-262/6.0/#sec-ecmascript-language-types) 2125 | * of `Object`. (e.g. arrays, functions, objects, regexes, `new Number(0)`, and `new String('')`) 2126 | * 2127 | * @static 2128 | * @memberOf _ 2129 | * @since 0.1.0 2130 | * @category Lang 2131 | * @param {*} value The value to check. 2132 | * @returns {boolean} Returns `true` if `value` is an object, else `false`. 2133 | * @example 2134 | * 2135 | * _.isObject({}); 2136 | * // => true 2137 | * 2138 | * _.isObject([1, 2, 3]); 2139 | * // => true 2140 | * 2141 | * _.isObject(_.noop); 2142 | * // => true 2143 | * 2144 | * _.isObject(null); 2145 | * // => false 2146 | */ 2147 | function isObject(value) { 2148 | var type = typeof value; 2149 | return !!value && (type == 'object' || type == 'function'); 2150 | } 2151 | 2152 | /** 2153 | * Checks if `value` is object-like. A value is object-like if it's not `null` 2154 | * and has a `typeof` result of "object". 2155 | * 2156 | * @static 2157 | * @memberOf _ 2158 | * @since 4.0.0 2159 | * @category Lang 2160 | * @param {*} value The value to check. 2161 | * @returns {boolean} Returns `true` if `value` is object-like, else `false`. 2162 | * @example 2163 | * 2164 | * _.isObjectLike({}); 2165 | * // => true 2166 | * 2167 | * _.isObjectLike([1, 2, 3]); 2168 | * // => true 2169 | * 2170 | * _.isObjectLike(_.noop); 2171 | * // => false 2172 | * 2173 | * _.isObjectLike(null); 2174 | * // => false 2175 | */ 2176 | function isObjectLike(value) { 2177 | return !!value && typeof value == 'object'; 2178 | } 2179 | 2180 | /** 2181 | * Checks if `value` is a native function. 2182 | * 2183 | * @static 2184 | * @memberOf _ 2185 | * @since 3.0.0 2186 | * @category Lang 2187 | * @param {*} value The value to check. 2188 | * @returns {boolean} Returns `true` if `value` is a native function, 2189 | * else `false`. 2190 | * @example 2191 | * 2192 | * _.isNative(Array.prototype.push); 2193 | * // => true 2194 | * 2195 | * _.isNative(_); 2196 | * // => false 2197 | */ 2198 | function isNative(value) { 2199 | if (!isObject(value)) { 2200 | return false; 2201 | } 2202 | var pattern = (isFunction(value) || isHostObject(value)) ? reIsNative : reIsHostCtor; 2203 | return pattern.test(toSource(value)); 2204 | } 2205 | 2206 | /** 2207 | * Checks if `value` is a plain object, that is, an object created by the 2208 | * `Object` constructor or one with a `[[Prototype]]` of `null`. 2209 | * 2210 | * @static 2211 | * @memberOf _ 2212 | * @since 0.8.0 2213 | * @category Lang 2214 | * @param {*} value The value to check. 2215 | * @returns {boolean} Returns `true` if `value` is a plain object, 2216 | * else `false`. 2217 | * @example 2218 | * 2219 | * function Foo() { 2220 | * this.a = 1; 2221 | * } 2222 | * 2223 | * _.isPlainObject(new Foo); 2224 | * // => false 2225 | * 2226 | * _.isPlainObject([1, 2, 3]); 2227 | * // => false 2228 | * 2229 | * _.isPlainObject({ 'x': 0, 'y': 0 }); 2230 | * // => true 2231 | * 2232 | * _.isPlainObject(Object.create(null)); 2233 | * // => true 2234 | */ 2235 | function isPlainObject(value) { 2236 | if (!isObjectLike(value) || 2237 | objectToString.call(value) != objectTag || isHostObject(value)) { 2238 | return false; 2239 | } 2240 | var proto = getPrototype(value); 2241 | if (proto === null) { 2242 | return true; 2243 | } 2244 | var Ctor = hasOwnProperty.call(proto, 'constructor') && proto.constructor; 2245 | return (typeof Ctor == 'function' && 2246 | Ctor instanceof Ctor && funcToString.call(Ctor) == objectCtorString); 2247 | } 2248 | 2249 | /** 2250 | * Checks if `value` is classified as a `String` primitive or object. 2251 | * 2252 | * @static 2253 | * @since 0.1.0 2254 | * @memberOf _ 2255 | * @category Lang 2256 | * @param {*} value The value to check. 2257 | * @returns {boolean} Returns `true` if `value` is correctly classified, 2258 | * else `false`. 2259 | * @example 2260 | * 2261 | * _.isString('abc'); 2262 | * // => true 2263 | * 2264 | * _.isString(1); 2265 | * // => false 2266 | */ 2267 | function isString(value) { 2268 | return typeof value == 'string' || 2269 | (!isArray(value) && isObjectLike(value) && objectToString.call(value) == stringTag); 2270 | } 2271 | 2272 | /** 2273 | * Checks if `value` is classified as a `Symbol` primitive or object. 2274 | * 2275 | * @static 2276 | * @memberOf _ 2277 | * @since 4.0.0 2278 | * @category Lang 2279 | * @param {*} value The value to check. 2280 | * @returns {boolean} Returns `true` if `value` is correctly classified, 2281 | * else `false`. 2282 | * @example 2283 | * 2284 | * _.isSymbol(Symbol.iterator); 2285 | * // => true 2286 | * 2287 | * _.isSymbol('abc'); 2288 | * // => false 2289 | */ 2290 | function isSymbol(value) { 2291 | return typeof value == 'symbol' || 2292 | (isObjectLike(value) && objectToString.call(value) == symbolTag); 2293 | } 2294 | 2295 | /** 2296 | * Checks if `value` is classified as a typed array. 2297 | * 2298 | * @static 2299 | * @memberOf _ 2300 | * @since 3.0.0 2301 | * @category Lang 2302 | * @param {*} value The value to check. 2303 | * @returns {boolean} Returns `true` if `value` is correctly classified, 2304 | * else `false`. 2305 | * @example 2306 | * 2307 | * _.isTypedArray(new Uint8Array); 2308 | * // => true 2309 | * 2310 | * _.isTypedArray([]); 2311 | * // => false 2312 | */ 2313 | function isTypedArray(value) { 2314 | return isObjectLike(value) && 2315 | isLength(value.length) && !!typedArrayTags[objectToString.call(value)]; 2316 | } 2317 | 2318 | /** 2319 | * Converts `value` to a finite number. 2320 | * 2321 | * @static 2322 | * @memberOf _ 2323 | * @since 4.12.0 2324 | * @category Lang 2325 | * @param {*} value The value to convert. 2326 | * @returns {number} Returns the converted number. 2327 | * @example 2328 | * 2329 | * _.toFinite(3.2); 2330 | * // => 3.2 2331 | * 2332 | * _.toFinite(Number.MIN_VALUE); 2333 | * // => 5e-324 2334 | * 2335 | * _.toFinite(Infinity); 2336 | * // => 1.7976931348623157e+308 2337 | * 2338 | * _.toFinite('3.2'); 2339 | * // => 3.2 2340 | */ 2341 | function toFinite(value) { 2342 | if (!value) { 2343 | return value === 0 ? value : 0; 2344 | } 2345 | value = toNumber(value); 2346 | if (value === INFINITY || value === -INFINITY) { 2347 | var sign = (value < 0 ? -1 : 1); 2348 | return sign * MAX_INTEGER; 2349 | } 2350 | return value === value ? value : 0; 2351 | } 2352 | 2353 | /** 2354 | * Converts `value` to an integer. 2355 | * 2356 | * **Note:** This function is loosely based on 2357 | * [`ToInteger`](http://www.ecma-international.org/ecma-262/6.0/#sec-tointeger). 2358 | * 2359 | * @static 2360 | * @memberOf _ 2361 | * @since 4.0.0 2362 | * @category Lang 2363 | * @param {*} value The value to convert. 2364 | * @returns {number} Returns the converted integer. 2365 | * @example 2366 | * 2367 | * _.toInteger(3.2); 2368 | * // => 3 2369 | * 2370 | * _.toInteger(Number.MIN_VALUE); 2371 | * // => 0 2372 | * 2373 | * _.toInteger(Infinity); 2374 | * // => 1.7976931348623157e+308 2375 | * 2376 | * _.toInteger('3.2'); 2377 | * // => 3 2378 | */ 2379 | function toInteger(value) { 2380 | var result = toFinite(value), 2381 | remainder = result % 1; 2382 | 2383 | return result === result ? (remainder ? result - remainder : result) : 0; 2384 | } 2385 | 2386 | /** 2387 | * Converts `value` to a number. 2388 | * 2389 | * @static 2390 | * @memberOf _ 2391 | * @since 4.0.0 2392 | * @category Lang 2393 | * @param {*} value The value to process. 2394 | * @returns {number} Returns the number. 2395 | * @example 2396 | * 2397 | * _.toNumber(3.2); 2398 | * // => 3.2 2399 | * 2400 | * _.toNumber(Number.MIN_VALUE); 2401 | * // => 5e-324 2402 | * 2403 | * _.toNumber(Infinity); 2404 | * // => Infinity 2405 | * 2406 | * _.toNumber('3.2'); 2407 | * // => 3.2 2408 | */ 2409 | function toNumber(value) { 2410 | if (typeof value == 'number') { 2411 | return value; 2412 | } 2413 | if (isSymbol(value)) { 2414 | return NAN; 2415 | } 2416 | if (isObject(value)) { 2417 | var other = isFunction(value.valueOf) ? value.valueOf() : value; 2418 | value = isObject(other) ? (other + '') : other; 2419 | } 2420 | if (typeof value != 'string') { 2421 | return value === 0 ? value : +value; 2422 | } 2423 | value = value.replace(reTrim, ''); 2424 | var isBinary = reIsBinary.test(value); 2425 | return (isBinary || reIsOctal.test(value)) 2426 | ? freeParseInt(value.slice(2), isBinary ? 2 : 8) 2427 | : (reIsBadHex.test(value) ? NAN : +value); 2428 | } 2429 | 2430 | /** 2431 | * Converts `value` to a plain object flattening inherited enumerable string 2432 | * keyed properties of `value` to own properties of the plain object. 2433 | * 2434 | * @static 2435 | * @memberOf _ 2436 | * @since 3.0.0 2437 | * @category Lang 2438 | * @param {*} value The value to convert. 2439 | * @returns {Object} Returns the converted plain object. 2440 | * @example 2441 | * 2442 | * function Foo() { 2443 | * this.b = 2; 2444 | * } 2445 | * 2446 | * Foo.prototype.c = 3; 2447 | * 2448 | * _.assign({ 'a': 1 }, new Foo); 2449 | * // => { 'a': 1, 'b': 2 } 2450 | * 2451 | * _.assign({ 'a': 1 }, _.toPlainObject(new Foo)); 2452 | * // => { 'a': 1, 'b': 2, 'c': 3 } 2453 | */ 2454 | function toPlainObject(value) { 2455 | return copyObject(value, keysIn(value)); 2456 | } 2457 | 2458 | /*------------------------------------------------------------------------*/ 2459 | 2460 | /** 2461 | * Creates an array of the own enumerable property names of `object`. 2462 | * 2463 | * **Note:** Non-object values are coerced to objects. See the 2464 | * [ES spec](http://ecma-international.org/ecma-262/6.0/#sec-object.keys) 2465 | * for more details. 2466 | * 2467 | * @static 2468 | * @since 0.1.0 2469 | * @memberOf _ 2470 | * @category Object 2471 | * @param {Object} object The object to query. 2472 | * @returns {Array} Returns the array of property names. 2473 | * @example 2474 | * 2475 | * function Foo() { 2476 | * this.a = 1; 2477 | * this.b = 2; 2478 | * } 2479 | * 2480 | * Foo.prototype.c = 3; 2481 | * 2482 | * _.keys(new Foo); 2483 | * // => ['a', 'b'] (iteration order is not guaranteed) 2484 | * 2485 | * _.keys('hi'); 2486 | * // => ['0', '1'] 2487 | */ 2488 | function keys(object) { 2489 | var isProto = isPrototype(object); 2490 | if (!(isProto || isArrayLike(object))) { 2491 | return baseKeys(object); 2492 | } 2493 | var indexes = indexKeys(object), 2494 | skipIndexes = !!indexes, 2495 | result = indexes || [], 2496 | length = result.length; 2497 | 2498 | for (var key in object) { 2499 | if (baseHas(object, key) && 2500 | !(skipIndexes && (key == 'length' || isIndex(key, length))) && 2501 | !(isProto && key == 'constructor')) { 2502 | result.push(key); 2503 | } 2504 | } 2505 | return result; 2506 | } 2507 | 2508 | /** 2509 | * Creates an array of the own and inherited enumerable property names of `object`. 2510 | * 2511 | * **Note:** Non-object values are coerced to objects. 2512 | * 2513 | * @static 2514 | * @memberOf _ 2515 | * @since 3.0.0 2516 | * @category Object 2517 | * @param {Object} object The object to query. 2518 | * @returns {Array} Returns the array of property names. 2519 | * @example 2520 | * 2521 | * function Foo() { 2522 | * this.a = 1; 2523 | * this.b = 2; 2524 | * } 2525 | * 2526 | * Foo.prototype.c = 3; 2527 | * 2528 | * _.keysIn(new Foo); 2529 | * // => ['a', 'b', 'c'] (iteration order is not guaranteed) 2530 | */ 2531 | function keysIn(object) { 2532 | var index = -1, 2533 | isProto = isPrototype(object), 2534 | props = baseKeysIn(object), 2535 | propsLength = props.length, 2536 | indexes = indexKeys(object), 2537 | skipIndexes = !!indexes, 2538 | result = indexes || [], 2539 | length = result.length; 2540 | 2541 | while (++index < propsLength) { 2542 | var key = props[index]; 2543 | if (!(skipIndexes && (key == 'length' || isIndex(key, length))) && 2544 | !(key == 'constructor' && (isProto || !hasOwnProperty.call(object, key)))) { 2545 | result.push(key); 2546 | } 2547 | } 2548 | return result; 2549 | } 2550 | 2551 | /** 2552 | * This method is like `_.assign` except that it recursively merges own and 2553 | * inherited enumerable string keyed properties of source objects into the 2554 | * destination object. Source properties that resolve to `undefined` are 2555 | * skipped if a destination value exists. Array and plain object properties 2556 | * are merged recursively. Other objects and value types are overridden by 2557 | * assignment. Source objects are applied from left to right. Subsequent 2558 | * sources overwrite property assignments of previous sources. 2559 | * 2560 | * **Note:** This method mutates `object`. 2561 | * 2562 | * @static 2563 | * @memberOf _ 2564 | * @since 0.5.0 2565 | * @category Object 2566 | * @param {Object} object The destination object. 2567 | * @param {...Object} [sources] The source objects. 2568 | * @returns {Object} Returns `object`. 2569 | * @example 2570 | * 2571 | * var users = { 2572 | * 'data': [{ 'user': 'barney' }, { 'user': 'fred' }] 2573 | * }; 2574 | * 2575 | * var ages = { 2576 | * 'data': [{ 'age': 36 }, { 'age': 40 }] 2577 | * }; 2578 | * 2579 | * _.merge(users, ages); 2580 | * // => { 'data': [{ 'user': 'barney', 'age': 36 }, { 'user': 'fred', 'age': 40 }] } 2581 | */ 2582 | var merge = createAssigner(function(object, source, srcIndex) { 2583 | baseMerge(object, source, srcIndex); 2584 | }); 2585 | 2586 | /*------------------------------------------------------------------------*/ 2587 | 2588 | /** 2589 | * Creates a function that returns `value`. 2590 | * 2591 | * @static 2592 | * @memberOf _ 2593 | * @since 2.4.0 2594 | * @category Util 2595 | * @param {*} value The value to return from the new function. 2596 | * @returns {Function} Returns the new constant function. 2597 | * @example 2598 | * 2599 | * var object = { 'user': 'fred' }; 2600 | * var getter = _.constant(object); 2601 | * 2602 | * getter() === object; 2603 | * // => true 2604 | */ 2605 | function constant(value) { 2606 | return function() { 2607 | return value; 2608 | }; 2609 | } 2610 | 2611 | /*------------------------------------------------------------------------*/ 2612 | 2613 | // Add methods that return wrapped values in chain sequences. 2614 | lodash.constant = constant; 2615 | lodash.keys = keys; 2616 | lodash.keysIn = keysIn; 2617 | lodash.merge = merge; 2618 | lodash.rest = rest; 2619 | lodash.toPlainObject = toPlainObject; 2620 | 2621 | /*------------------------------------------------------------------------*/ 2622 | 2623 | // Add methods that return unwrapped values in chain sequences. 2624 | lodash.cloneDeep = cloneDeep; 2625 | lodash.eq = eq; 2626 | lodash.isArguments = isArguments; 2627 | lodash.isArray = isArray; 2628 | lodash.isArrayLike = isArrayLike; 2629 | lodash.isArrayLikeObject = isArrayLikeObject; 2630 | lodash.isBuffer = isBuffer; 2631 | lodash.isFunction = isFunction; 2632 | lodash.isLength = isLength; 2633 | lodash.isNative = isNative; 2634 | lodash.isObject = isObject; 2635 | lodash.isObjectLike = isObjectLike; 2636 | lodash.isPlainObject = isPlainObject; 2637 | lodash.isString = isString; 2638 | lodash.isSymbol = isSymbol; 2639 | lodash.isTypedArray = isTypedArray; 2640 | lodash.toFinite = toFinite; 2641 | lodash.toInteger = toInteger; 2642 | lodash.toNumber = toNumber; 2643 | 2644 | /*------------------------------------------------------------------------*/ 2645 | 2646 | /** 2647 | * The semantic version number. 2648 | * 2649 | * @static 2650 | * @memberOf _ 2651 | * @type {string} 2652 | */ 2653 | lodash.VERSION = VERSION; 2654 | 2655 | /*--------------------------------------------------------------------------*/ 2656 | 2657 | // Expose Lodash on the free variable `window` or `self` when available so it's 2658 | // globally accessible, even when bundled with Browserify, Webpack, etc. This 2659 | // also prevents errors in cases where Lodash is loaded by a script tag in the 2660 | // presence of an AMD loader. See http://requirejs.org/docs/errors.html#mismatch 2661 | // for more details. Use `_.noConflict` to remove Lodash from the global object. 2662 | (freeWindow || freeSelf || {})._ = lodash; 2663 | 2664 | if (freeExports && freeModule) { 2665 | // Export for Node.js. 2666 | if (moduleExports) { 2667 | (freeModule.exports = lodash)._ = lodash; 2668 | } 2669 | // Export for CommonJS support. 2670 | freeExports._ = lodash; 2671 | } 2672 | }.call(this)); 2673 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eazy-logger", 3 | "description": "Simple cli logger", 4 | "version": "4.0.1", 5 | "homepage": "https://github.com/shakyshane/easy-logger", 6 | "author": { 7 | "name": "Shane Osbourne" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git://github.com/shakyshane/easy-logger.git" 12 | }, 13 | "bugs": { 14 | "url": "https://github.com/shakyshane/easy-logger/issues" 15 | }, 16 | "licenses": [ 17 | { 18 | "type": "Apache 2.0", 19 | "url": "https://github.com/shakyshane/easy-logger/blob/master/LICENSE" 20 | } 21 | ], 22 | "files": [ 23 | "index.js", 24 | "lodash.custom.js", 25 | "example.js" 26 | ], 27 | "main": "index.js", 28 | "engines": { 29 | "node": ">= 0.8.0" 30 | }, 31 | "scripts": { 32 | "lint": "jshint index.js test/*.js", 33 | "test": "npm run lint && mocha", 34 | "lodash": "lodash include=cloneDeep,merge exports=node" 35 | }, 36 | "dependencies": { 37 | "chalk": "4.1.2" 38 | }, 39 | "devDependencies": { 40 | "chai": "^3.5.0", 41 | "jshint": "^2.6.0", 42 | "lodash-cli": "4.12.0", 43 | "mocha": "^10.2.0", 44 | "sinon": "^1.12.2", 45 | "strip-ansi": "^6.0.1" 46 | }, 47 | "keywords": [ 48 | "plugins" 49 | ] 50 | } 51 | -------------------------------------------------------------------------------- /test/log.js: -------------------------------------------------------------------------------- 1 | var assert = require("chai").assert; 2 | var sinon = require("sinon"); 3 | var easyLogger = require("../index"); 4 | var stripColor = require("strip-ansi"); 5 | var chalk = require("chalk"); 6 | 7 | var defaultConfig = { 8 | prefix: `${chalk.blue("[")}${chalk.magenta("logger")}${chalk.cyan("]")} `, 9 | prefixes: { 10 | debug: "DEBUG ", 11 | info: "INFO ", 12 | warn: "WARN ", 13 | error: "ERROR " 14 | } 15 | }; 16 | 17 | // helper to get un-coloured strings for comparisons 18 | var arg = function (spy, num, argNum) { 19 | var call = spy.getCall(num); 20 | var arg; 21 | if (call.args) { 22 | arg = call.args[argNum]; 23 | } 24 | return stripColor(arg); 25 | }; 26 | 27 | describe("Logging", function(){ 28 | var spy, logger; 29 | before(function () { 30 | spy = sinon.spy(console, "log"); 31 | }); 32 | beforeEach(function () { 33 | logger = easyLogger.Logger(defaultConfig); 34 | }); 35 | after(function () { 36 | spy.restore(); 37 | }); 38 | afterEach(function () { 39 | spy.reset(); 40 | logger.reset(); 41 | }); 42 | it("can do console.log on info", function(){ 43 | logger.log("info", "Running!"); 44 | var actual = arg(spy, 0, 0); 45 | var expected = "[logger] Running!"; 46 | assert.equal(actual, expected); 47 | }); 48 | it("Does not log when level = info & log msg is WARN", function(){ 49 | logger.log("warn", "Not found"); 50 | sinon.assert.notCalled(spy); 51 | }); 52 | it("DOES log after the log level is rest", function(){ 53 | logger.log("warn", "Not found"); 54 | sinon.assert.notCalled(spy); 55 | logger.setLevel("warn"); 56 | logger.log("warn", "Not found"); 57 | var actual = arg(spy, 0, 0); 58 | var expected = "[logger] Not found"; 59 | assert.equal(actual, expected); 60 | logger.setLevel("error"); 61 | logger.log("info", "Welcome!"); 62 | sinon.assert.calledOnce(spy); 63 | }); 64 | it("Can remove the prefix", function(){ 65 | logger.unprefixed("info", ""); 66 | sinon.assert.calledWithExactly(spy, ""); 67 | }); 68 | it("Can remove the level prefixes", function(){ 69 | logger.setLevelPrefixes(true); 70 | logger.unprefixed("info", ""); 71 | var actual = arg(spy, 0, 0); 72 | var expected = ""; 73 | assert.equal(actual, expected); 74 | }); 75 | it("Can use the level prefixes", function(){ 76 | logger.setLevelPrefixes(true); 77 | logger.log("info", ""); 78 | var actual = arg(spy, 0, 0); 79 | var expected = "[logger] INFO "; 80 | assert.equal(actual, expected); 81 | }); 82 | it("Can return a cloned logger", function(){ 83 | var clone = logger.clone(); 84 | clone.setLevelPrefixes(true); 85 | clone.log("info", ""); 86 | var actual = arg(spy, 0, 0); 87 | var expected = "[logger] INFO "; 88 | assert.equal(actual, expected); 89 | }); 90 | it("Can return multiple cloned loggers", function() { 91 | var clone = logger.clone(function (config) { 92 | config.prefix = config.prefix + "shane "; 93 | return config; 94 | }); 95 | 96 | clone.setLevelPrefixes(true); 97 | clone.log("info", ""); 98 | 99 | var actual = arg(spy, 0, 0); 100 | var expected = "[logger] shane INFO "; 101 | 102 | assert.equal(actual, expected); 103 | 104 | // Second clone 105 | var clone2 = logger.clone(function (config) { 106 | config.prefix = config.prefix + "Second "; 107 | return config; 108 | }); 109 | 110 | clone2.setLevelPrefixes(true); 111 | clone2.log("info", ""); 112 | 113 | actual = arg(spy, 1, 0); 114 | expected = "[logger] Second INFO "; 115 | assert.equal(actual, expected); 116 | }); 117 | it("Can return a cloned logger with different prefix", function(){ 118 | var clone = logger.clone({prefix: "SHANE "}); 119 | clone.setLevelPrefixes(true); 120 | clone.log("info", ""); 121 | var actual = arg(spy, 0, 0); 122 | var expected = "SHANE INFO "; 123 | assert.equal(actual, expected); 124 | }); 125 | it("Can give a callback for creating new config", function(){ 126 | var clone = logger.clone(function (config) { 127 | config.prefix = "SHANE "; 128 | return config; 129 | }); 130 | clone.setLevelPrefixes(true); 131 | clone.log("info", ""); 132 | var actual = arg(spy, 0, 0); 133 | var expected = "SHANE INFO "; 134 | assert.equal(actual, expected); 135 | }); 136 | it("Can append to existing prefix via callback", function(){ 137 | var logger = new easyLogger.Logger(defaultConfig); 138 | var clone = logger.clone(function (config) { 139 | config.prefix = config.prefix + "[new module] "; 140 | return config; 141 | }); 142 | clone.log("info", ""); 143 | var actual = arg(spy, 0, 0); 144 | var expected = "[logger] [new module] "; 145 | assert.equal(actual, expected); 146 | }); 147 | it("Can append to existing prefix via callback with level prefixes", function(){ 148 | var logger = new easyLogger.Logger(defaultConfig); 149 | var clone = logger.clone(function (config) { 150 | config.prefix = config.prefix + "[new module] "; 151 | return config; 152 | }); 153 | clone.setLevelPrefixes(true); 154 | clone.log("info", ""); 155 | var actual = arg(spy, 0, 0); 156 | var expected = "[logger] [new module] INFO "; 157 | assert.equal(actual, expected); 158 | }); 159 | it("can use built-in string replacement", function(){ 160 | var logger = new easyLogger.Logger(defaultConfig); 161 | logger.setLevelPrefixes(true); 162 | logger.log("info", "", "http://shakyshane.com/js.js"); 163 | 164 | var actual = arg(spy, 0, 0); 165 | var expected = "[logger] INFO "; 166 | 167 | assert.equal(actual, expected); 168 | 169 | actual = arg(spy, 0, 1); 170 | assert.equal(actual, "http://shakyshane.com/js.js"); 171 | 172 | }); 173 | it("can use built-in string replacement (2)", function(){ 174 | var logger = new easyLogger.Logger(defaultConfig); 175 | logger.setLevelPrefixes(true); 176 | logger.log("info", "", "http://shakyshane.com/", "js.js"); 177 | var actual = arg(spy, 0, 0); 178 | var expected = "[logger] INFO "; 179 | assert.equal(actual, expected); 180 | actual = arg(spy, 0, 1); 181 | assert.equal(actual, "http://shakyshane.com/"); 182 | actual = arg(spy, 0, 2); 183 | assert.equal(actual, "js.js"); 184 | }); 185 | it("can be used with no configuration", function () { 186 | var logger = new easyLogger.Logger(defaultConfig); 187 | logger.log("no config"); 188 | }); 189 | it("can use alias methods (INFO)", function () { 190 | var logger = new easyLogger.Logger(defaultConfig); 191 | logger.setLevelPrefixes(true); 192 | logger.info(""); 193 | var actual = arg(spy, 0, 0); 194 | var expected = "[logger] INFO "; 195 | assert.equal(actual, expected); 196 | }); 197 | it("can use alias methods (INFO)", function () { 198 | var logger = new easyLogger.Logger(defaultConfig); 199 | logger.setLevelPrefixes(true); 200 | logger.info(""); 201 | var actual = arg(spy, 0, 0); 202 | var expected = "[logger] INFO "; 203 | assert.equal(actual, expected); 204 | }); 205 | it("can chain from alias methods", function () { 206 | var logger = new easyLogger.Logger(defaultConfig); 207 | logger.setLevelPrefixes(true).info("").setLevelPrefixes(false); 208 | var actual = arg(spy, 0, 0); 209 | var expected = "[logger] INFO "; 210 | assert.equal(actual, expected); 211 | }); 212 | it("can set an option once", function () { 213 | var logger = new easyLogger.Logger(defaultConfig); 214 | logger.setOnce("useLevelPrefixes", true).info(""); 215 | var actual = arg(spy, 0, 0); 216 | var expected = "[logger] INFO "; 217 | assert.equal(actual, expected); 218 | 219 | logger.info(""); 220 | actual = arg(spy, 1, 0); 221 | expected = "[logger] "; 222 | assert.equal(actual, expected); 223 | }); 224 | it("can set an option once multiple times", function () { 225 | var logger = new easyLogger.Logger(defaultConfig); 226 | logger.setOnce("useLevelPrefixes", true); 227 | logger.setOnce("useLevelPrefixes", true); 228 | logger.info(""); 229 | 230 | var actual = arg(spy, 0, 0); 231 | var expected = "[logger] INFO "; 232 | assert.equal(actual, expected); 233 | 234 | logger.info(""); 235 | actual = arg(spy, 1, 0); 236 | expected = "[logger] "; 237 | assert.equal(actual, expected); 238 | }); 239 | it("can be muted", function () { 240 | var logger = new easyLogger.Logger(defaultConfig); 241 | logger.mute(true); 242 | logger.info(""); 243 | sinon.assert.notCalled(spy); 244 | }); 245 | it("can be un-muted", function () { 246 | var logger = new easyLogger.Logger(defaultConfig); 247 | logger.mute(true); 248 | logger.info(""); 249 | sinon.assert.notCalled(spy); 250 | logger.mute(false); 251 | logger.info(""); 252 | var actual = arg(spy, 0, 0); 253 | var expected = "[logger] "; 254 | assert.equal(actual, expected); 255 | }); 256 | it("can accept a function for the prefix", function(){ 257 | var logger = new easyLogger.Logger({ 258 | prefix: function () { 259 | return "PREFIX"; 260 | } 261 | }); 262 | logger.info(""); 263 | 264 | var actual = arg(spy, 0, 0); 265 | var expected = "PREFIX"; 266 | assert.equal(actual, expected); 267 | }); 268 | it("can SET a function for the prefix", function(){ 269 | var logger = new easyLogger.Logger(defaultConfig); 270 | logger.setPrefix(function () { 271 | return "PREFIX"; 272 | }); 273 | logger.info(""); 274 | 275 | var actual = arg(spy, 0, 0); 276 | var expected = "PREFIX"; 277 | assert.equal(actual, expected); 278 | }); 279 | it("can update the prefix", function(){ 280 | var logger = new easyLogger.Logger(defaultConfig); 281 | logger.setPrefix("SHANE"); 282 | logger.info(""); 283 | 284 | var actual = arg(spy, 0, 0); 285 | var expected = "SHANE"; 286 | assert.equal(actual, expected); 287 | }); 288 | it("can update the prefix with color included", function(){ 289 | var logger = new easyLogger.Logger(defaultConfig); 290 | logger.info(""); 291 | 292 | var actual = arg(spy, 0, 0); 293 | var expected = "[logger] "; 294 | assert.equal(actual, expected); 295 | 296 | logger.setPrefix("ERROR: "); 297 | logger.info(""); 298 | actual = arg(spy, 1, 0); 299 | expected = "ERROR: "; 300 | assert.equal(actual, expected); 301 | }); 302 | it("can print javascript", function(){ 303 | var logger = new easyLogger.Logger(defaultConfig); 304 | logger.info(`(function() { console.log("lol!") })()`); 305 | 306 | var actual = arg(spy, 0, 0); 307 | var expected = `[logger] (function() { console.log("lol!") })()`; 308 | 309 | assert.equal(actual, expected); 310 | }); 311 | 312 | it("should handle prototype pollution attempts safely", function () { 313 | const lib = easyLogger; 314 | console.log("Before Attack: ", JSON.stringify( Object.getPrototypeOf({}))); 315 | 316 | try { 317 | // for multiple functions, uncomment only one for each execution. 318 | lib.Logger(JSON.parse("{\"__proto__\":{\"pollutedKey\":123}}")); 319 | } catch (e) { 320 | } 321 | 322 | console.log("After Attack: ", JSON.stringify(Object.getPrototypeOf({}))); 323 | 324 | assert.notProperty(Object.prototype, "pollutedKey", "Prototype pollution occurred"); 325 | 326 | // Cleanup if any property was added 327 | delete Object.prototype.pollutedKey; 328 | }); 329 | }); 330 | --------------------------------------------------------------------------------