├── .eslintrc.js ├── .gitignore ├── .npmignore ├── .todo.txt ├── config ├── done.txt ├── report.txt ├── todo.txt └── todo.txt.bak ├── CHANGELOG ├── LICENSE ├── Makefile ├── README.md ├── SECURITY.md ├── bench └── path-vs-dotPath.js ├── browser ├── tree-kit.js └── tree-kit.min.js ├── documentation.md ├── lib ├── arrayLike.js ├── browser.js ├── clone.js ├── diff.js ├── dotPath.js ├── extend.js ├── lazy.js ├── mask.js ├── path.js ├── tree.js └── wildDotPath.js ├── log └── README ├── package.json ├── sample ├── bigDeepObject.js ├── bigFlatObject.js ├── garbageStringObject.js ├── sample1.json └── stringFlatObject.js ├── test ├── arrayLike-test.js ├── clone-test.js ├── diff-test.js ├── dotPath-test.js ├── extend-test.js ├── lazy-test.js ├── mask-test.js ├── path-test.js └── wildDotPath-test.js └── wfm.json /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | 'root': true , 3 | 'env': { 4 | 'browser': true , 5 | 'node': true , 6 | 'es6': true , 7 | 'es2022': true 8 | } , 9 | 'parserOptions': { 10 | 'ecmaVersion': 2022 11 | } , 12 | 'extends': [ 'eslint:recommended' ] , 13 | 'ignorePatterns': [ "*.min.js" ] , 14 | 'rules': { 15 | 16 | /* 17 | Bad code -- detect anything that can be broken or lead to bugs 18 | */ 19 | 20 | 21 | 22 | 'strict': [ 'error' , 'global' ] , 23 | 'unicode-bom': [ 'error' , 'never' ] , 24 | 'radix': 'error' , 25 | 'eqeqeq': 'error' , 26 | 'consistent-return': 'off' , 27 | 'valid-typeof': 'error' , 28 | 'no-unneeded-ternary': 'error' , 29 | 'no-unused-vars': 'warn' , // During development phase, it's boring to clean unused var since they can be used later 30 | 'no-lonely-if': 'off' , // Can hurt semantic programming 31 | 'no-nested-ternary': 'off' , // Now I use the streamlined ternary operator a lot 32 | 'no-shadow': 'error' , 33 | 'no-shadow-restricted-names': 'error' , 34 | 'require-atomic-updates': 'off' , // check for possible race condition on assignment, interesting but too nitpicky 35 | 36 | 37 | 38 | /* 39 | Code preferences 40 | */ 41 | 42 | 43 | 44 | 'prefer-arrow-callback': 'error' , 45 | 'prefer-spread': 'warn' , 46 | 'prefer-rest-params': 'warn' , 47 | 'no-control-regex': 'off' , // because thing like \x00 are considered like a control even if escaped... 48 | 'no-fallthrough': 'off' , 49 | 'no-empty': [ 'error' , { 50 | 'allowEmptyCatch': true 51 | } ] , 52 | 53 | 54 | 55 | /* 56 | Coding styles -- cosmetic rules and opinionated preferences 57 | */ 58 | 59 | 60 | 61 | // Indent & spaces (general) 62 | 'indent': [ 'error' , 'tab' , { 63 | 'SwitchCase': 1 , 64 | 'MemberExpression': 1 , 65 | 'flatTernaryExpressions': true 66 | } ] , 67 | 'newline-per-chained-call': 'off', 68 | 'no-multi-spaces': 'off' , 69 | 'block-spacing': 'error' , 70 | 'comma-spacing': [ 'error' , { 71 | 'before': true , 72 | 'after': true 73 | } ] , 74 | 'no-whitespace-before-property': 'error' , 75 | 'space-before-blocks': 'error' , 76 | 'space-before-function-paren': [ 'error' , { 77 | 'anonymous': 'never', 78 | 'named': 'never', 79 | 'asyncArrow': 'always' 80 | } ] , 81 | 'space-infix-ops': 'error' , 82 | 'space-unary-ops': [ 'error' , { 83 | 'words': true , 84 | 'nonwords': true , 85 | 'overrides': { 86 | //'-': false , 87 | } 88 | } ] , 89 | 'space-in-parens': [ 'error' , 'always' , { 90 | 'exceptions': [ 'empty' ] 91 | } ] , 92 | 'no-trailing-spaces': 'error' , 93 | 'switch-colon-spacing': [ 'error' , { 94 | 'after': true , 95 | 'before': true 96 | } ] , 97 | 'arrow-spacing': 'error' , 98 | 'rest-spread-spacing': [ 'error' , 'always' ] , 99 | /* Troublesome with commented line of code 100 | 'spaced-comment': [ 'error' , 'always' , { 101 | 'line': { 102 | 'markers': [ '/' ], 103 | 'exceptions': [ '-', '*', '/' ] 104 | } , 105 | 'block': { 106 | 'exceptions': [ '*' ] , 107 | 'balanced': true 108 | } 109 | } ] , 110 | */ 111 | 112 | 113 | // Semi-colon 114 | 'semi': [ 'error' , 'always' ] , 115 | 'semi-style': [ 'error' , 'last' ] , 116 | 'semi-spacing': [ 'error' , { 117 | 'before': true , 118 | 'after': true 119 | } ] , 120 | 121 | // Objects 122 | 'key-spacing': [ 'error' , { 123 | 'beforeColon': false , 124 | 'afterColon': true , 125 | 'mode': 'strict' 126 | } ] , 127 | 'object-curly-newline': [ 'error' , { 128 | 'ObjectExpression' : { 129 | 'consistent': true , 130 | 'minProperties': 4 131 | } , 132 | 'ObjectPattern' : { 133 | // object destructuring assigment 134 | 'consistent': true , 135 | 'minProperties': 8 136 | } 137 | } ] , 138 | 'object-curly-spacing': [ 'error' , 'always' ] , 139 | 'object-property-newline': [ 'error' , { 'allowMultiplePropertiesPerLine': true } ] , 140 | 141 | 142 | // Arrays 143 | 'array-bracket-newline': [ 'error' , 'consistent' ] , 144 | //'array-element-newline': [ 'error' , { 'multiline': true , 'minItems': 5 } ] , 145 | 'array-bracket-spacing': [ 'error' , 'always' ], 146 | 147 | 'brace-style': [ 'error' , 'stroustrup' , { 148 | 'allowSingleLine': true 149 | } ] , 150 | 151 | 152 | // Misc style 153 | 'no-else-return': 'warn' , 154 | 'comma-dangle': [ 'error' , 'never' ] , 155 | 'quotes': 'off' , 156 | 'camelcase': 'warn' , 157 | 158 | 159 | 160 | /* 161 | Method limitation 162 | */ 163 | 164 | 165 | 166 | 'no-console': 'off' 167 | } 168 | } ; 169 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Specific # 2 | ############ 3 | 4 | *.local.* 5 | *.local 6 | *.log 7 | *.html.gz 8 | *.css.gz 9 | *.js.gz 10 | .spellcast 11 | build 12 | _build 13 | _templates 14 | _static 15 | 16 | 17 | # gitignore / Node.gitignore # 18 | ############################## 19 | lib-cov 20 | lcov.info 21 | *.seed 22 | *.log 23 | *.csv 24 | *.dat 25 | *.out 26 | *.pid 27 | *.gz 28 | 29 | pids 30 | logs 31 | results 32 | build 33 | .grunt 34 | package-lock.json 35 | 36 | node_modules 37 | 38 | 39 | # OS generated files # 40 | ###################### 41 | .DS_Store 42 | .DS_Store? 43 | ._* 44 | .Spotlight-V100 45 | .Trashes 46 | Icon? 47 | ehthumbs.db 48 | Thumbs.db 49 | 50 | 51 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # +++ .gitignore 2 | # Specific # 3 | ############ 4 | 5 | *.local.* 6 | *.local 7 | *.log 8 | *.html.gz 9 | *.css.gz 10 | *.js.gz 11 | .spellcast 12 | build 13 | _build 14 | _templates 15 | _static 16 | 17 | 18 | # gitignore / Node.gitignore # 19 | ############################## 20 | lib-cov 21 | lcov.info 22 | *.seed 23 | *.log 24 | *.csv 25 | *.dat 26 | *.out 27 | *.pid 28 | *.gz 29 | 30 | pids 31 | logs 32 | results 33 | build 34 | .grunt 35 | package-lock.json 36 | 37 | node_modules 38 | 39 | 40 | # OS generated files # 41 | ###################### 42 | .DS_Store 43 | .DS_Store? 44 | ._* 45 | .Spotlight-V100 46 | .Trashes 47 | Icon? 48 | ehthumbs.db 49 | Thumbs.db 50 | 51 | 52 | 53 | # --- .gitignore 54 | test 55 | log 56 | sample 57 | wfm.json 58 | builder 59 | test/ 60 | doc/ 61 | sample/ 62 | builder/ 63 | -------------------------------------------------------------------------------- /.todo.txt/config: -------------------------------------------------------------------------------- 1 | # === EDIT FILE LOCATIONS BELOW === 2 | 3 | PROJECT_NAME='Tree-kit' 4 | 5 | echo -ne "\n\t\e[0;35m~~> \e[1;35m$PROJECT_NAME\e[0;35m todo-list <~~\e[0m\n\n" 6 | 7 | # My config 8 | export TODOTXT_DEFAULT_ACTION=ls 9 | 10 | # Your todo.txt directory 11 | export TODO_DIR=$(dirname "$TODOTXT_CFG_FILE") 12 | 13 | # Your todo/done/report.txt locations 14 | export TODO_FILE="$TODO_DIR/todo.txt" 15 | export DONE_FILE="$TODO_DIR/done.txt" 16 | export REPORT_FILE="$TODO_DIR/report.txt" 17 | 18 | # You can customize your actions directory location 19 | #export TODO_ACTIONS_DIR="$HOME/.todo.actions.d" 20 | 21 | # == EDIT FILE LOCATIONS ABOVE === 22 | 23 | # === COLOR MAP === 24 | 25 | ## Text coloring and formatting is done by inserting ANSI escape codes. 26 | ## If you have re-mapped your color codes, or use the todo.txt 27 | ## output in another output system (like Conky), you may need to 28 | ## over-ride by uncommenting and editing these defaults. 29 | ## If you change any of these here, you also need to uncomment 30 | ## the defaults in the COLORS section below. Otherwise, todo.txt 31 | ## will still use the defaults! 32 | 33 | # export BLACK='\\033[0;30m' 34 | # export RED='\\033[0;31m' 35 | # export GREEN='\\033[0;32m' 36 | # export BROWN='\\033[0;33m' 37 | # export BLUE='\\033[0;34m' 38 | # export PURPLE='\\033[0;35m' 39 | # export CYAN='\\033[0;36m' 40 | # export LIGHT_GREY='\\033[0;37m' 41 | # export DARK_GREY='\\033[1;30m' 42 | # export LIGHT_RED='\\033[1;31m' 43 | # export LIGHT_GREEN='\\033[1;32m' 44 | # export YELLOW='\\033[1;33m' 45 | # export LIGHT_BLUE='\\033[1;34m' 46 | # export LIGHT_PURPLE='\\033[1;35m' 47 | # export LIGHT_CYAN='\\033[1;36m' 48 | # export WHITE='\\033[1;37m' 49 | # export DEFAULT='\\033[0m' 50 | 51 | # === COLORS === 52 | 53 | ## Uncomment and edit to override these defaults. 54 | ## Reference the constants from the color map above, 55 | ## or use $NONE to disable highlighting. 56 | # 57 | # Priorities can be any upper-case letter. 58 | # A,B,C are highlighted; you can add coloring for more. 59 | # 60 | # export PRI_A=$YELLOW # color for A priority 61 | # export PRI_B=$GREEN # color for B priority 62 | # export PRI_C=$LIGHT_BLUE # color for C priority 63 | # export PRI_D=... # define your own 64 | # export PRI_X=$WHITE # color unless explicitly defined 65 | 66 | # There is highlighting for tasks that have been done, 67 | # but haven't been archived yet. 68 | # 69 | # export COLOR_DONE=$LIGHT_GREY 70 | 71 | # There is highlighting for projects and contexts. 72 | # 73 | # export COLOR_PROJECT=$RED 74 | # export COLOR_CONTEXT=$RED 75 | 76 | # === BEHAVIOR === 77 | 78 | ## customize list output 79 | # 80 | # TODOTXT_SORT_COMMAND will filter after line numbers are 81 | # inserted, but before colorization, and before hiding of 82 | # priority, context, and project. 83 | # 84 | # export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2' 85 | 86 | # TODOTXT_FINAL_FILTER will filter list output after colorization, 87 | # priority hiding, context hiding, and project hiding. That is, 88 | # just before the list output is displayed. 89 | # 90 | # export TODOTXT_FINAL_FILTER='cat' 91 | -------------------------------------------------------------------------------- /.todo.txt/done.txt: -------------------------------------------------------------------------------- 1 | x 2014-11-04 +extend deep:true bug with function, there are copied as undefined @bug 2 | x 2014-11-04 +extend document 'deepFunc' option @doc 3 | x 2014-12-23 +extend finish the 'descriptor' option: what to do with 'deep' when a property is a getter/setter? @code 4 | x 2015-01-09 +extend options 'descriptor': copy property descriptor as well, using Object.getOwnPropertyDescriptor() & Object.defineProperty() @code 5 | x 2015-01-09 +extend options 'nonEnum': get non-enumerable properties as well, using Object.getOwnPropertyNames() @code 6 | x 2015-01-10 +extend still trouble with getter: direct access rather than descriptor.value on the /!\ mark @bug 7 | x 2015-01-10 +extend document 'circular' option @doc 8 | x 2015-01-10 +extend document 'maxDepth' option @doc 9 | -------------------------------------------------------------------------------- /.todo.txt/report.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cronvel/tree-kit/881c286f9cf9342fecd3ac04a8f31956949058d3/.todo.txt/report.txt -------------------------------------------------------------------------------- /.todo.txt/todo.txt: -------------------------------------------------------------------------------- 1 | +defineLazyProperty document it @doc 2 | do not mix 'you' and 'we' in the @doc 3 | +path document it @doc 4 | (B) +path path as array should support array walking @code 5 | -------------------------------------------------------------------------------- /.todo.txt/todo.txt.bak: -------------------------------------------------------------------------------- 1 | +defineLazyProperty document it @doc 2 | x 2015-01-10 +extend document 'maxDepth' option @doc 3 | do not mix 'you' and 'we' in the @doc 4 | -------------------------------------------------------------------------------- /CHANGELOG: -------------------------------------------------------------------------------- 1 | 2 | v0.8.8 3 | ------ 4 | 5 | Fix wildDotPath when the wildcard is at the end of the path 6 | 7 | 8 | v0.8.7 9 | ------ 10 | 11 | New array-like toolbox 12 | 13 | 14 | v0.8.6 15 | ------ 16 | 17 | Expose .toPathArray() in dotPath and wildDotPath 18 | 19 | 20 | v0.8.5 21 | ------ 22 | 23 | dotPath/wildDotPath .delete() minor fix 24 | 25 | 26 | v0.8.4 27 | ------ 28 | 29 | wildDotPath now has all things in dotPath 30 | 31 | 32 | v0.8.3 33 | ------ 34 | 35 | Fix missing require for new wildDotPath 36 | 37 | 38 | v0.8.2 39 | ------ 40 | 41 | New: wildDotPath.get() and wildDotPath.getPathValue() (more methods will be added later, e.g. wildDotPath.set()) 42 | 43 | 44 | v0.7.5 45 | ------ 46 | 47 | Fix prototype pollution in .extend() when the 'unflat' option is set 48 | 49 | 50 | v0.7.4 51 | ------ 52 | 53 | dotPath: empty path part support 54 | 55 | 56 | v0.7.3 57 | ------ 58 | 59 | .extend() option 'mask' now supports a number, the rank at which the masking starts 60 | 61 | 62 | v0.7.2 63 | ------ 64 | 65 | .extend(): fix the 'preserve' option bug when replacing with an object. New option 'mask' that is the revert of 'preserve': only update an object, but do not create new keys 66 | 67 | 68 | v0.7.1 69 | ------ 70 | 71 | path/dotPath with path in array mode now checks that there is no object in the array 72 | 73 | 74 | v0.7.0 75 | ------ 76 | 77 | BREAKING CHANGE -- .path()/.dotPath(): drop the function's subtree support, fix prototype pollution 78 | 79 | 80 | v0.6.2 81 | ------ 82 | 83 | .clone() with Date support 84 | 85 | 86 | v0.6.1 87 | ------ 88 | 89 | New: .dotPath.*(): a faster alternative to .path.*(), supporting only dot-separated path 90 | 91 | 92 | v0.6.0 93 | ------ 94 | 95 | Breaking change: .extend() 'deepFilter' option is gone 'deepFilter.whitelist' has moved to 'deep' and 'deepFilter.blacklist' has moved to 'immutables' 96 | 97 | 98 | v0.5.27 99 | ------- 100 | 101 | Fixed a vulnerability in .extend(), moved to ESLint, moved to Tea-Time builtin 'expect' 102 | 103 | 104 | v0.5.26 105 | ------- 106 | 107 | New: Browser lib! 108 | 109 | 110 | v0.5.25 111 | ------- 112 | 113 | tree.path -- new operations: concat and insert 114 | 115 | 116 | v0.5.24 117 | ------- 118 | 119 | IMPORTANT BUGFIX: clone was not working well with arrays 120 | 121 | 122 | v0.5.23 123 | ------- 124 | 125 | clone interface 126 | 127 | 128 | v0.5.22 129 | ------- 130 | 131 | Tree.path.* now supports empty keys 132 | 133 | 134 | v0.5.21 135 | ------- 136 | 137 | tree.path() now return undefined if the source object, well, is not an object (instead of throwing) 138 | 139 | 140 | v0.5.20 141 | ------- 142 | 143 | Bugfix: make require( 'tree-kit/lib/path.js' ) works as expected (was encapsulating everything in a 'path' sub-object) 144 | 145 | 146 | v0.5.19 147 | ------- 148 | 149 | New: tree.path.autoPush() 150 | 151 | 152 | v0.5.18 153 | ------- 154 | 155 | tree.json was removed (get its own module: json-kit) 156 | 157 | 158 | v0.5.17 159 | ------- 160 | 161 | json: parser run a bit faster 162 | 163 | 164 | v0.5.16 165 | ------- 166 | 167 | json: parser run a bit faster 168 | 169 | 170 | v0.5.15 171 | ------- 172 | 173 | json: parser is now working 174 | 175 | 176 | v0.5.14 177 | ------- 178 | 179 | json.stringify() improvements 180 | 181 | 182 | v0.5.13 183 | ------- 184 | 185 | "use strict" everywhere 186 | 187 | 188 | v0.5.12 189 | ------- 190 | 191 | Bechmark: ubench v0.2.x ; "use strict" everywhere 192 | 193 | 194 | v0.5.11 195 | ------- 196 | 197 | ubench benchmark 198 | 199 | 200 | v0.5.10 201 | ------- 202 | 203 | json: some fixes 204 | 205 | 206 | v0.5.9 207 | ------ 208 | 209 | json.stringify() is now on par with native JSON.stringify()! 210 | 211 | 212 | v0.5.8 213 | ------ 214 | 215 | json utilities: wip 216 | 217 | 218 | v0.5.7 219 | ------ 220 | 221 | New: tree.path.append() and tree.path.prepend() 222 | 223 | 224 | v0.5.6 225 | ------ 226 | 227 | New feature: tree.path() now support the bracket syntax for arrays. 228 | 229 | 230 | v0.5.5 231 | ------ 232 | 233 | Documentation: just added link to http://blog.soulserv.net/tag/tree-kit that points to tree-kit tutorials. 234 | 235 | 236 | v0.5.4 237 | ------ 238 | 239 | path: all methods return the targeted object like path.get() does. 240 | 241 | 242 | v0.5.3 243 | ------ 244 | 245 | New path.*() method: path.define(), like set, but only if the targeted item does not exist 246 | 247 | 248 | v0.5.2 249 | ------ 250 | 251 | New path.*() methods: path.inc() & path.dec(), that increment and decrement values 252 | 253 | 254 | v0.5.1 255 | ------ 256 | 257 | path.*(): 258 | * tree.path.prototype can be used for inheritance, using Object.create( tree.path.prototype ) 259 | * path.*() now supports path as array too (but it's still not done for array walking) 260 | 261 | 262 | v0.5.0 263 | ------ 264 | 265 | path.*(): pseudo-element notation use '#' instead of ':' 266 | 267 | 268 | v0.4.3 - v0.4.4 269 | --------------- 270 | 271 | path.*() now support a semi-colon syntax for accessing arrays, featuring pseudo-element like :last and :next, etc. 272 | 273 | 274 | v0.4.2 275 | ------ 276 | 277 | New: path submodule featuring path.get(), path.set() and path.delete(). It allows setting, getting deleting by a 278 | dot-separated path scheme. 279 | 280 | 281 | v0.4.1 282 | ------ 283 | 284 | clone() have now its own module/file: clone.js. 285 | 286 | 287 | v0.4.0 288 | ------ 289 | 290 | extend(): 291 | * 'circular' & 'maxDepth' options finished, 'descriptor' option bugfix 292 | 293 | clone(): 294 | * does not depend upon extend() anymore, it has been fully rewritten with optimization in mind 295 | * it does not use recursive function call but loops, which is super-efficient ;) 296 | * it now accepts a *circular* boolean argument, just like extend(), see the doc! 297 | 298 | 299 | v0.3.5 300 | ------ 301 | 302 | Doc: table of content. 303 | 304 | 305 | v0.3.4 306 | ------ 307 | 308 | New method: clone(), providing the best object-cloning facility that this lib can offer. 309 | 310 | 311 | v0.3.3 312 | ------ 313 | 314 | extend() 315 | - 'nonEnum' option that copy non-enumerable properties as well (in conjunction with 'own') 316 | - 'descriptor' option that preserve property's descriptor 317 | 318 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Cédric Ronvel 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # User rules 5 | 6 | # The first rule is the default rule, when invoking "make" without argument... 7 | # Build every buildable things 8 | all: install doc browser 9 | 10 | # Just install things so it works, basicaly: it just performs a "npm install --production" ATM 11 | install: log/npm-install.log 12 | 13 | # Just install things so it works, basicaly: it just performs a "npm install" ATM 14 | dev-install: log/npm-dev-install.log 15 | 16 | # Build 17 | build: browser 18 | 19 | # Build the browser lib 20 | browser: browser/tree-kit.js browser/tree-kit.min.js 21 | 22 | # This run the JsHint & Mocha BDD test, display it to STDOUT & save it to log/mocha.log and log/jshint.log 23 | test: log/jshint.log log/mocha.log 24 | 25 | # This run the JsHint, display it to STDOUT & save it to log/jshint.log 26 | lint: log/jshint.log 27 | 28 | # This run the Mocha BDD test, display it to STDOUT & save it to log/mocha.log 29 | unit: log/mocha.log 30 | 31 | # Performs the browser tests 32 | browser-test: log/testling.log 33 | 34 | # This build the doc and README.md 35 | doc: README.md 36 | 37 | # This publish to NPM and push to Github, if we are on master branch only 38 | publish: log/npm-publish.log log/github-push.log 39 | 40 | # Clean temporary things, or things that can be automatically regenerated 41 | clean: clean-all 42 | 43 | 44 | 45 | # Variables 46 | 47 | MOCHA=mocha 48 | JSHINT=./node_modules/jshint/bin/jshint --verbose 49 | BROWSERIFY=./node_modules/.bin/browserify 50 | UGLIFY=./node_modules/.bin/uglifyjs 51 | 52 | 53 | 54 | # Files rules 55 | 56 | # Build the browser lib 57 | browser/tree-kit.js: lib/*.js 58 | ${BROWSERIFY} lib/browser.js -s treeKit -o browser/tree-kit.js 59 | 60 | # Build the browser minified lib 61 | browser/tree-kit.min.js: browser/tree-kit.js 62 | ${UGLIFY} browser/tree-kit.js -o browser/tree-kit.min.js -m 63 | 64 | # JsHint STDOUT test 65 | log/jshint.log: log/npm-dev-install.log lib/*.js test/*.js 66 | ${JSHINT} lib/*.js test/*.js | tee log/jshint.log ; exit $${PIPESTATUS[0]} 67 | 68 | # Mocha BDD STDOUT test 69 | log/mocha.log: log/npm-dev-install.log lib/*.js test/*.js 70 | ${MOCHA} test/*.js -R spec | tee log/mocha.log ; exit $${PIPESTATUS[0]} 71 | 72 | # README 73 | README.md: documentation.md 74 | cat documentation.md > README.md 75 | 76 | # Mocha Markdown BDD spec 77 | bdd-spec.md: log/npm-dev-install.log lib/*.js test/*.js 78 | ${MOCHA} test/*.js -R markdown > bdd-spec.md 79 | 80 | # Upgrade version in package.json 81 | log/upgrade-package.log: lib/*.js test/*.js documentation.md 82 | npm version patch -m "Upgrade package.json version to %s" | tee log/upgrade-package.log ; exit $${PIPESTATUS[0]} 83 | 84 | # Publish to NPM 85 | log/npm-publish.log: check-if-master-branch log/upgrade-package.log 86 | npm publish | tee log/npm-publish.log ; exit $${PIPESTATUS[0]} 87 | 88 | # Push to Github/master 89 | log/github-push.log: lib/*.js test/*.js package.json 90 | #'npm version patch' create the git tag by itself... 91 | #git tag v`cat package.json | grep version | sed -r 's/.*"([0-9.]*)".*/\1/'` 92 | git push origin master --tags | tee log/github-push.log ; exit $${PIPESTATUS[0]} 93 | 94 | # NPM install 95 | log/npm-install.log: package.json 96 | npm install --production | tee log/npm-install.log ; exit $${PIPESTATUS[0]} 97 | 98 | # NPM install for developpement usage 99 | log/npm-dev-install.log: package.json 100 | npm install | tee log/npm-dev-install.log ; exit $${PIPESTATUS[0]} 101 | 102 | 103 | 104 | # PHONY rules 105 | 106 | .PHONY: clean-all check-if-master-branch 107 | 108 | # Delete files, mostly log and non-versioned files 109 | clean-all: 110 | rm -rf log/*.log README.md bdd-spec.md node_modules 111 | 112 | # This will fail if we are not on master branch (grep exit 1 if nothing found) 113 | check-if-master-branch: 114 | git branch | grep "^* master$$" 115 | 116 | 117 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # Tree Kit 3 | 4 | This lib is a toolbox that provide functions to operate with nested `Object` structure. 5 | It features the best `.extend()` method, providing dozen of options that all others libs miss. 6 | 7 | * License: MIT 8 | * Current status: release candidate 9 | * Platform: Node.js only (browser support is planned) 10 | 11 | Some tutorials are available at [blog.soulserv.net/tag/tree-kit](http://blog.soulserv.net/tag/tree-kit/). 12 | 13 | 14 | 15 | # Install 16 | 17 | Use Node Package Manager: 18 | 19 | npm install tree-kit 20 | 21 | 22 | 23 | # Library references 24 | 25 | * [.extend()](#ref.extend): full-featured extend facility, copy, clone, extend 26 | * [.clone()](#ref.clone): clone any object 27 | * [.diff()](#ref.diff): report differences between two objects 28 | 29 | 30 | 31 | In all examples below, it is assumed that you have required the lib into the `tree` variable: 32 | ```js 33 | var tree = require( 'tree-kit' ) ; 34 | ``` 35 | 36 | 37 | 38 | 39 | ## .extend( options , target , source1 , [source2] , [...] ) 40 | 41 | * options `Object` extend options, it supports the properties: 42 | * own `boolean` only copy enumerable own properties from the sources 43 | * nonEnum `boolean` copy non-enumerable properties as well, works only with own:true 44 | * descriptor `boolean` preserve property's descriptor (i.e. writable, enumerable, configurable, get & set) 45 | * deep: `boolean` or `Array` or `Set`, if true perform a deep (recursive) extend, if it is an Array/Set of prototypes, only deep-copy 46 | objects of those prototypes (it is a replacement for deepFilter.whitelist which was removed in Tree Kit 0.6) 47 | * immutables: an Array/Set of immutable object's prototypes that are filtered out for deep-copy (it is a replacement 48 | for deepFilter.blacklist which was removed in Tree Kit 0.6) 49 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected 50 | (referenced), if false then nested object are blindly cloned 51 | * maxDepth `integer` used in conjunction with deep, when the max depth is reached an exception is raised, it defaults to 100 52 | when the 'circular' option is off, or defaults to null if 'circular' is on 53 | * move `boolean` move properties from the sources object to the target object (delete properties from the sources object) 54 | * preserve `boolean` existing properties in the target object will not be overwritten 55 | * nofunc `boolean` skip properties that are functions 56 | * deepFunc `boolean` in conjunction with 'deep', this will process sources functions like objects rather than 57 | copying/referencing them directly into the source (default behaviour), thus, the result will not be a function, 58 | it forces 'deep' options 59 | * proto `boolean` alter the target's prototype so that it matches the source's prototype. 60 | It forces option 'own'. Specifying multiple sources does not make sens here. 61 | * inherit `boolean` make the target inherit from the source (the target's prototype will be the source itself, not its prototype). 62 | It forces option 'own' and disable 'proto'. Specifying multiple sources does not make sens here. 63 | * skipRoot `boolean` prevent the prototype of the target **root** object from mutation. 64 | Only nested objects' prototype will be mutated. 65 | * flat `boolean|string` sources properties are copied in a way to produce a *flat* target, the target's key 66 | is the full path (separated by '.') of the source's key, also if a string is provided it will be used as 67 | the path separator 68 | * unflat `boolean|string` it is the opposite of 'flat': assuming that the sources are in the *flat* format, 69 | it expands all flat properties -- whose name are path with '.' as the separator -- deeply into the target, 70 | also if a string is provided it will be used as the path separator 71 | * target `Object` the target of the extend, properties will be copied to this object 72 | * source1 `Object` the source of the extend, properties will be copied from this object 73 | * ... 74 | 75 | This is a full-featured *extend* of an object with one or more source object. 76 | 77 | It is easily translated from jQuery-like *extend()*: 78 | * `extend( target , source )` translate into `tree.extend( null , target , source )` 79 | * `extend( true , target , source )` translate into `tree.extend( { deep: true } , target , source )` 80 | 81 | However, here we have full control over what will be extended and how. 82 | 83 | **All the options above are inactive by default**. 84 | You can pass null as argument #0 to get the default behaviour (= all options are inactive). 85 | So using the default behaviour, `tree.extend()` will copy all enumerable properties, and perform a shallow copy (a nested object 86 | is not cloned, it remains a reference of the original one). 87 | 88 | With the *deep* option, a deep copy is performed, so nested object are cloned too. 89 | 90 | The *own* option clone only owned properties from the sources, properties that are part of the source's prototype would not 91 | be copied/cloned. 92 | 93 | The *nonEnum* option will clone properties that are not enumerable. 94 | 95 | The *descriptor* option will preserve property's descriptor, e.g. if the source property is not writable and not enumerable, 96 | so will be the copied property. 97 | 98 | In case of a *getter* properties: 99 | 100 | * without the *descriptor* option, the getter function of the source object will be called, the return value will be put 101 | into the target property (so it lose its getter/setter behaviour) 102 | * with the *descriptor* option, the getter & setter function of the source object will be copied (but not called) into the target 103 | property: the getter/setter behaviour is preserved 104 | 105 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it. 106 | We can see this *circular* feature in action in [this example](#example.circular). 107 | 108 | Mixing *inherit* and *deep* provides a nice multi-level inheritance. 109 | 110 | With the *flat* option example: 111 | ```js 112 | var o = { 113 | one: 1, 114 | sub: { 115 | two: 2, 116 | three: 3 117 | } 118 | } ; 119 | 120 | var flatCopy = tree.extend( { flat: true } , {} , o ) ; 121 | ``` 122 | ... it will produce: 123 | ```js 124 | { 125 | one: 1, 126 | "sub.two": 2, 127 | "sub.three": 3 128 | } 129 | ``` 130 | 131 | By the way, the *unflat* option does the opposite, and thus can reverse this back to the original form. 132 | 133 | The *deepFilter* option is used when you do not want to clone some type of object. 134 | Let's say you want a deep copy except for `Buffer` objects, you simply want them to share the same reference: 135 | ```js 136 | var o = { 137 | one: '1' , 138 | buf: new Buffer( "My buffer" ) , 139 | subtree: { 140 | two: 2 , 141 | three: 'THREE' 142 | } 143 | } ; 144 | 145 | // either 146 | var extended1 = tree.extend( { deep: true, deepFilter: { whitelist: [ Object.prototype ] } } , {} , o ) ; 147 | // or 148 | var extended2 = tree.extend( { deep: true, deepFilter: { blacklist: [ Buffer.prototype ] } } , {} , o ) ; 149 | ``` 150 | 151 | Doing this, we have `o.buf === extended1.buf === extended2.buf`, and `o.subtree !== extended1.subtree !== extended2.subtree`. 152 | 153 | 154 | 155 | 156 | ## .clone( original , [circular] ) 157 | 158 | * original `Object` the source object to clone 159 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected 160 | (referenced), if false then nested object are blindly cloned 161 | 162 | It returns a clone of the *original* object, providing the best object-cloning facility that this lib can offer. 163 | 164 | The clone produced are perfect independant copy **in 99% of use case**, but there is one big limitation: 165 | method that access variables in the parent's scope. 166 | 167 | The clone will share those variables with the *original* object, so they are not totally independant entity. 168 | Design pattern using closure to emulate *private member* (e.g. the revealing pattern) can cause trouble. 169 | 170 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it. 171 | 172 | 173 | Here is an example of this *circular* feature: 174 | ```js 175 | var o = { 176 | a: 'a', 177 | sub: { 178 | b: 'b' 179 | }, 180 | sub2: { 181 | c: 'c' 182 | } 183 | } ; 184 | 185 | o.loop = o ; 186 | o.sub.loop = o ; 187 | o.subcopy = o.sub ; 188 | o.sub.link = o.sub2 ; 189 | o.sub2.link = o.sub ; 190 | 191 | var c = tree.clone( o , true ) ; 192 | 193 | expect( c.loop ).to.be( c ) ; 194 | expect( c.sub ).to.be( c.subcopy ) ; 195 | expect( c.sub.loop ).to.be( c ) ; 196 | expect( c.subcopy.loop ).to.be( c ) ; 197 | expect( c.sub.link ).to.be( c.sub2 ) ; 198 | expect( c.sub2.link ).to.be( c.sub ) ; 199 | ``` 200 | 201 | ... without *circular* on, the `clone()` method would run forever, creating a new object independant nested object each time 202 | it reaches the *loop* property. 203 | We can see that the *subcopy* property remains a reference of *sub* even in the clone, thanks to the *circular* option. 204 | 205 | However, if we are sure that there isn't multiple reference to the same object or circular references, we can gain a lot of 206 | performances by leaving that options off. 207 | It can save a lot of `.indexOf()` call on big data structure. 208 | 209 | This method does not uses `extend()` anymore like in version 0.3.x, it now uses its own optimized code. 210 | However it is equivalent to an `extend()` with those options turned on: *deep, own, nonEnum, descriptor & proto*. 211 | If *circular* is on, it has the same effect than the `extend()`'s *circular* option. 212 | 213 | **Also please note that design pattern emulating private members using a closure's scope cannot be truly cloned** 214 | (e.g. the *revealing pattern*). 215 | This is not possible to mutate a function's scope. 216 | So the clone's methods will continue to inherit the parent's scope of the original function. 217 | 218 | 219 | 220 | 221 | ## .diff( left , right , [options] ) 222 | 223 | * left `Object` the left-hand side object structure 224 | * right `Object` the right-hand side object structure 225 | * options `Object` containing options, it supports: 226 | * path `string` the initial path, default: empty string 227 | * pathSeparator `string` the path separator, default: '.' 228 | 229 | This tool reports diff between a left-hand side and right-hand side object structure. 230 | It returns an object, each key is a path where a difference is reported, the value being an object containing (again) the path 231 | and a human-readable message. 232 | 233 | See this example: 234 | ```js 235 | var left = { 236 | a: 'a', 237 | b: 2, 238 | c: 'three', 239 | sub: { 240 | e: 5, 241 | f: 'six', 242 | } 243 | } ; 244 | 245 | var right = { 246 | b: 2, 247 | c: 3, 248 | d: 'dee', 249 | sub: { 250 | e: 5, 251 | f: 6, 252 | } 253 | } ; 254 | 255 | console.log( tree.diff( a , b ) ) ; 256 | ``` 257 | It will output: 258 | ```js 259 | { '.a': { path: '.a', message: 'does not exist in right-hand side' }, 260 | '.c': { path: '.c', message: 'different typeof: string - number' }, 261 | '.sub.f': { path: '.sub.f', message: 'different typeof: string - number' }, 262 | '.d': { path: '.d', message: 'does not exist in left-hand side' } } 263 | ``` 264 | 265 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | ## Reporting a Vulnerability 3 | 4 | If you think you have found a vulnerability, _please report responsibly_. 5 | Don't create GitHub issues for security issues. 6 | Instead, send an email to cedric dot ronvel at gmail dot com and I will look into it as soon as possible. 7 | 8 | **A note for bounty hunters:** I should mention that I *usually* prefer to fix security issues by myself, 9 | because it could involve rethinking API or fixing it / working around it in a way only an official maintainer can do it. 10 | I want to avoid people getting frustrated: **don't work on a fix before getting in touch with me**. 11 | -------------------------------------------------------------------------------- /bench/path-vs-dotPath.js: -------------------------------------------------------------------------------- 1 | 2 | "use strict" ; 3 | 4 | /* global benchmark, competitor */ 5 | 6 | 7 | 8 | var path = require( '../lib/path.js' ) ; 9 | var dotPath = require( '../lib/dotPath.js' ) ; 10 | 11 | 12 | 13 | benchmark( 'get a value through a path' , () => { 14 | var data = { 15 | propertyA: "value1" , 16 | propertyB: "value2" , 17 | object: { 18 | propertyC: "value3" , 19 | propertyD: "value4" , 20 | long: { nested: { path: { to: { a: { value: {} } } } } } 21 | } , 22 | array: [ 'element1' , 'element2' , 'element3' , [ 'element4' , 'element5' , 'element6' , [ 'element7' , 'element8' , 'element9' ] ] ] 23 | } ; 24 | 25 | competitor( 'path.get()' , () => { 26 | var output ; 27 | 28 | output = path.get( data , 'propertyA' ) ; 29 | output = path.get( data , 'object' ) ; 30 | output = path.get( data , 'object.propertyC' ) ; 31 | output = path.get( data , 'object.long.nested.path.to.a.value' ) ; 32 | output = path.get( data , 'array' ) ; 33 | output = path.get( data , 'array.0' ) ; 34 | output = path.get( data , 'array.1' ) ; 35 | output = path.get( data , 'array.3' ) ; 36 | output = path.get( data , 'array.3.1' ) ; 37 | output = path.get( data , 'array.3.3.1' ) ; 38 | } ) ; 39 | 40 | competitor( 'path.get() with path-array' , () => { 41 | var output ; 42 | 43 | output = path.get( data , ['propertyA'] ) ; 44 | output = path.get( data , ['object'] ) ; 45 | output = path.get( data , ['object','propertyC'] ) ; 46 | output = path.get( data , ['object','long','nested','path','to','a','value'] ) ; 47 | output = path.get( data , ['array'] ) ; 48 | output = path.get( data , ['array',0] ) ; 49 | output = path.get( data , ['array',1] ) ; 50 | output = path.get( data , ['array',3] ) ; 51 | output = path.get( data , ['array',3,1] ) ; 52 | output = path.get( data , ['array',3,3,1] ) ; 53 | } ) ; 54 | 55 | competitor( 'dotPath.get()' , () => { 56 | var output ; 57 | 58 | output = dotPath.get( data , 'propertyA' ) ; 59 | output = dotPath.get( data , 'object' ) ; 60 | output = dotPath.get( data , 'object.propertyC' ) ; 61 | output = dotPath.get( data , 'object.long.nested.dotPath.to.a.value' ) ; 62 | output = dotPath.get( data , 'array' ) ; 63 | output = dotPath.get( data , 'array.0' ) ; 64 | output = dotPath.get( data , 'array.1' ) ; 65 | output = dotPath.get( data , 'array.3' ) ; 66 | output = dotPath.get( data , 'array.3.1' ) ; 67 | output = dotPath.get( data , 'array.3.3.1' ) ; 68 | } ) ; 69 | 70 | competitor( 'dotPath.get() with path-array' , () => { 71 | var output ; 72 | 73 | output = dotPath.get( data , ['propertyA'] ) ; 74 | output = dotPath.get( data , ['object'] ) ; 75 | output = dotPath.get( data , ['object','propertyC'] ) ; 76 | output = dotPath.get( data , ['object','long','nested','path','to','a','value'] ) ; 77 | output = dotPath.get( data , ['array'] ) ; 78 | output = dotPath.get( data , ['array',0] ) ; 79 | output = dotPath.get( data , ['array',1] ) ; 80 | output = dotPath.get( data , ['array',3] ) ; 81 | output = dotPath.get( data , ['array',3,1] ) ; 82 | output = dotPath.get( data , ['array',3,3,1] ) ; 83 | } ) ; 84 | } ) ; 85 | 86 | 87 | 88 | benchmark( 'get a value through a long path' , () => { 89 | var data = { 90 | propertyA: "value1" , 91 | propertyB: "value2" , 92 | object: { 93 | propertyC: "value3" , 94 | propertyD: "value4" , 95 | long: { nested: { path: { to: { a: { value: {} } } } } } 96 | } , 97 | array: [ 'element1' , 'element2' , 'element3' , [ 'element4' , 'element5' , 'element6' , [ 'element7' , 'element8' , 'element9' , [[['element10']]] ] ] ] 98 | } ; 99 | 100 | competitor( 'path.get()' , () => { 101 | var output ; 102 | output = path.get( data , 'object.long.nested.path.to.a.value' ) ; 103 | output = path.get( data , 'array.3.3.3.0.0.0' ) ; 104 | } ) ; 105 | 106 | competitor( 'path.get() with path-array' , () => { 107 | var output ; 108 | output = path.get( data , ['object','long','nested','path','to','a','value'] ) ; 109 | output = path.get( data , ['array',3,3,3,0,0,0] ) ; 110 | } ) ; 111 | 112 | competitor( 'dotPath.get()' , () => { 113 | var output ; 114 | output = dotPath.get( data , 'object.long.nested.dotPath.to.a.value' ) ; 115 | output = dotPath.get( data , 'array.3.3.3.0.0.0' ) ; 116 | } ) ; 117 | 118 | competitor( 'dotPath.get() with path-array' , () => { 119 | var output ; 120 | output = dotPath.get( data , ['object','long','nested','dotPath','to','a','value'] ) ; 121 | output = dotPath.get( data , ['array',3,3,3,0,0,0] ) ; 122 | } ) ; 123 | } ) ; 124 | 125 | -------------------------------------------------------------------------------- /browser/tree-kit.min.js: -------------------------------------------------------------------------------- 1 | (function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.treeKit=e()}})(function(){var e,t,r;return function(){function e(t,r,n){function o(i,u){if(!r[i]){if(!t[i]){var s="function"==typeof require&&require;if(!u&&s)return s(i,!0);if(f)return f(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var c=r[i]={exports:{}};t[i][0].call(c.exports,function(e){var r=t[i][1][e];return o(r||e)},c,c.exports,e,t,r,n)}return r[i].exports}for(var f="function"==typeof require&&require,i=0;i[e[0],t(e[1])]))};r.filter=function(e,t){if(!e||typeof e!=="object"){throw new Error("Expecting an object")}return Object.fromEntries(Object.entries(e).filter(e=>t(e[1])))}},{}],2:[function(e,t,r){"use strict";const n={};t.exports=n;n.extend=e("./extend.js");n.clone=e("./clone.js");n.path=e("./path.js");n.dotPath=e("./dotPath.js");n.wildDotPath=e("./wildDotPath.js");Object.assign(n,e("./arrayLike.js"))},{"./arrayLike.js":1,"./clone.js":3,"./dotPath.js":4,"./extend.js":5,"./path.js":6,"./wildDotPath.js":7}],3:[function(e,t,r){"use strict";function n(e,t){var r=Object.getPrototypeOf(e);if(n.opaque.has(r)){return n.opaque.get(r)(e)}var o,f,i,u,s,a,c=[{source:e,target:Array.isArray(e)?[]:Object.create(r)}],p=c[0].target,l=new Map;l.set(e,p);while(u=c.shift()){i=Object.getOwnPropertyNames(u.source);for(o=0;onew Date(e))},{}],4:[function(e,t,r){"use strict";const n={};t.exports=n;const o=[];const f="This would cause prototype pollution";function i(e){if(Array.isArray(e)){return e}if(!e){return o}if(typeof e==="string"){return e[e.length-1]==="."?e.slice(0,-1).split("."):e.split(".")}throw new TypeError("[tree.dotPath]: the path argument should be a string or an array")}n.toPathArray=i;function u(e,t,r=0){var n,o,i,u=e;for(n=0,o=t.length+r;nu(e,i(t)));n.set=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);u[o]=r;return r});n.define=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!(o in u)){u[o]=r}return u[o]});n.inc=((e,t)=>{if(!e||typeof e!=="object"){return}var r=i(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(f)}var o=s(e,r);if(typeof o[n]==="number"){o[n]++}else if(!o[n]||typeof o[n]!=="object"){o[n]=1}return o[n]});n.dec=((e,t)=>{if(!e||typeof e!=="object"){return}var r=i(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(f)}var o=s(e,r);if(typeof o[n]==="number"){o[n]--}else if(!o[n]||typeof o[n]!=="object"){o[n]=-1}return o[n]});n.concat=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=r}else if(Array.isArray(u[o])&&Array.isArray(r)){u[o]=u[o].concat(r)}return u[o]});n.insert=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=r}else if(Array.isArray(u[o])&&Array.isArray(r)){u[o]=r.concat(u[o])}return u[o]});n.delete=((e,t)=>{var r=i(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(f)}var o=u(e,r,-1);if(!o||typeof o!=="object"||!Object.hasOwn(o,n)){return false}delete o[n];return true});n.autoPush=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(u[o]===undefined){u[o]=r}else if(Array.isArray(u[o])){u[o].push(r)}else{u[o]=[u[o],r]}return u[o]});n.append=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=[r]}else if(Array.isArray(u[o])){u[o].push(r)}return u[o]});n.prepend=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=[r]}else if(Array.isArray(u[o])){u[o].unshift(r)}return u[o]})},{}],5:[function(e,t,r){"use strict";function n(e,t,...r){var n,f,i=false,u=r.length;if(!u){return t}if(!e||typeof e!=="object"){e={}}var s={depth:0,prefix:""};if(e.deep){if(Array.isArray(e.deep)){e.deep=new Set(e.deep)}else if(!(e.deep instanceof Set)){e.deep=true}}if(e.immutables){if(Array.isArray(e.immutables)){e.immutables=new Set(e.immutables)}else if(!(e.immutables instanceof Set)){delete e.immutables}}if(!e.maxDepth&&e.deep&&!e.circular){e.maxDepth=100}if(e.deepFunc){e.deep=true}if(e.flat){e.deep=true;e.proto=false;e.inherit=false;e.unflat=false;if(typeof e.flat!=="string"){e.flat="."}}if(e.unflat){e.deep=false;e.proto=false;e.inherit=false;e.flat=false;if(typeof e.unflat!=="string"){e.unflat="."}}if(e.inherit){e.own=true;e.proto=false}else if(e.proto){e.own=true}if(!t||typeof t!=="object"&&typeof t!=="function"){i=true}if(!e.skipRoot&&(e.inherit||e.proto)){for(n=u-1;n>=0;n--){f=r[n];if(f&&(typeof f==="object"||typeof f==="function")){if(e.inherit){if(i){t=Object.create(f)}else{Object.setPrototypeOf(t,f)}}else if(e.proto){if(i){t=Object.create(Object.getPrototypeOf(f))}else{Object.setPrototypeOf(t,Object.getPrototypeOf(f))}}break}}}else if(i){t={}}s.references={sources:[],targets:[]};for(n=0;nt.maxDepth){throw new Error("[tree] extend(): max depth reached("+t.maxDepth+")")}if(t.circular){e.references.sources.push(n);e.references.targets.push(r)}if(t.unflat&&e.depth===0){for(u in n){e.unflatKeys=u.split(t.unflat);e.unflatIndex=0;e.unflatFullKey=u;f(e,t,r,n,e.unflatKeys[e.unflatIndex],o)}delete e.unflatKeys;delete e.unflatIndex;delete e.unflatFullKey}else if(t.own){if(t.nonEnum){i=Object.getOwnPropertyNames(n)}else{i=Object.keys(n)}for(u of i){f(e,t,r,n,u,o)}}else{for(u in n){f(e,t,r,n,u,o)}}}function f(e,t,r,n,i,u){if(i==="__proto__"){return}let s,a,c;if(e.unflatKeys){if(e.unflatIndex=0){return}o({depth:e.depth+1,prefix:e.prefix+i+t.flat,references:e.references},t,r,s,u)}else{if(y>=0){l=e.references.targets[y];if(t.descriptor){Object.defineProperty(r,p,{value:l,enumerable:a.enumerable,writable:a.writable,configurable:a.configurable})}else{r[p]=l}return}if(!d||!Object.hasOwn(r,p)){if(Array.isArray(s)){l=[]}else if(t.proto){l=Object.create(c)}else if(t.inherit){l=Object.create(s)}else{l={}}if(t.descriptor){Object.defineProperty(r,p,{value:l,enumerable:a.enumerable,writable:a.writable,configurable:a.configurable})}else{r[p]=l}}else if(t.proto&&Object.getPrototypeOf(l)!==c){Object.setPrototypeOf(l,c)}else if(t.inherit&&Object.getPrototypeOf(l)!==s){Object.setPrototypeOf(l,s)}if(t.circular){e.references.sources.push(s);e.references.targets.push(l)}if(e.unflatKeys&&e.unflatIndexu(e,i(t)));n.getPaths=((e,t)=>p(e,i(t),a));n.getPathValueMap=((e,t)=>p(e,i(t),c));function l(e,t,r,n,o=0){var i,u,s=e;for(i=t.length-1;o{e[t]=r;return 1};n.set=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),d,r)});const y=(e,t,r)=>{if(t in e){return 0}e[t]=r;return 1};n.define=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),y,r)});const b=(e,t)=>{if(typeof e[t]==="number"){e[t]++}else if(!e[t]||typeof e[t]!=="object"){e[t]=1}return 1};n.inc=((e,t)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),b)});const h=(e,t)=>{if(typeof e[t]==="number"){e[t]--}else if(!e[t]||typeof e[t]!=="object"){e[t]=-1}return 1};n.dec=((e,t)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),h)});const j=(e,t,r)=>{if(!e[t]){e[t]=r}else if(Array.isArray(e[t])&&Array.isArray(r)){e[t]=e[t].concat(r)}return 1};n.concat=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),j,r)});const w=(e,t,r)=>{if(!e[t]){e[t]=r}else if(Array.isArray(e[t])&&Array.isArray(r)){e[t]=r.concat(e[t])}return 1};n.insert=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),w,r)});const g=(e,t)=>{if(!Object.hasOwn(e,t)){return 0}delete e[t];return 1};n.delete=((e,t)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),g)});const _=(e,t,r)=>{if(e[t]===undefined){e[t]=r}else if(Array.isArray(e[t])){e[t].push(r)}else{e[t]=[e[t],r]}return 1};n.autoPush=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),_,r)});const A=(e,t,r)=>{if(!e[t]){e[t]=[r]}else if(Array.isArray(e[t])){e[t].push(r)}return 1};n.append=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),A,r)});const v=(e,t,r)=>{if(!e[t]){e[t]=[r]}else if(Array.isArray(e[t])){e[t].unshift(r)}return 1};n.prepend=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),v,r)})},{}]},{},[2])(2)}); -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | 2 | # Tree Kit 3 | 4 | This lib is a toolbox that provide functions to operate with nested `Object` structure. 5 | It features the best `.extend()` method, providing dozen of options that all others libs miss. 6 | 7 | * License: MIT 8 | * Current status: release candidate 9 | * Platform: Node.js only (browser support is planned) 10 | 11 | Some tutorials are available at [blog.soulserv.net/tag/tree-kit](http://blog.soulserv.net/tag/tree-kit/). 12 | 13 | 14 | 15 | # Install 16 | 17 | Use Node Package Manager: 18 | 19 | npm install tree-kit 20 | 21 | 22 | 23 | # Library references 24 | 25 | * [.extend()](#ref.extend): full-featured extend facility, copy, clone, extend 26 | * [.clone()](#ref.clone): clone any object 27 | * [.diff()](#ref.diff): report differences between two objects 28 | 29 | 30 | 31 | In all examples below, it is assumed that you have required the lib into the `tree` variable: 32 | ```js 33 | var tree = require( 'tree-kit' ) ; 34 | ``` 35 | 36 | 37 | 38 | 39 | ## .extend( options , target , source1 , [source2] , [...] ) 40 | 41 | * options `Object` extend options, it supports the properties: 42 | * own `boolean` only copy enumerable own properties from the sources 43 | * nonEnum `boolean` copy non-enumerable properties as well, works only with own:true 44 | * descriptor `boolean` preserve property's descriptor (i.e. writable, enumerable, configurable, get & set) 45 | * deep: `boolean` or `Array` or `Set`, if true perform a deep (recursive) extend, if it is an Array/Set of prototypes, only deep-copy 46 | objects of those prototypes (it is a replacement for deepFilter.whitelist which was removed in Tree Kit 0.6) 47 | * immutables: an Array/Set of immutable object's prototypes that are filtered out for deep-copy (it is a replacement 48 | for deepFilter.blacklist which was removed in Tree Kit 0.6) 49 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected 50 | (referenced), if false then nested object are blindly cloned 51 | * maxDepth `integer` used in conjunction with deep, when the max depth is reached an exception is raised, it defaults to 100 52 | when the 'circular' option is off, or defaults to null if 'circular' is on 53 | * move `boolean` move properties from the sources object to the target object (delete properties from the sources object) 54 | * preserve `boolean` existing properties in the target object will not be overwritten 55 | * nofunc `boolean` skip properties that are functions 56 | * deepFunc `boolean` in conjunction with 'deep', this will process sources functions like objects rather than 57 | copying/referencing them directly into the source (default behaviour), thus, the result will not be a function, 58 | it forces 'deep' options 59 | * proto `boolean` alter the target's prototype so that it matches the source's prototype. 60 | It forces option 'own'. Specifying multiple sources does not make sens here. 61 | * inherit `boolean` make the target inherit from the source (the target's prototype will be the source itself, not its prototype). 62 | It forces option 'own' and disable 'proto'. Specifying multiple sources does not make sens here. 63 | * skipRoot `boolean` prevent the prototype of the target **root** object from mutation. 64 | Only nested objects' prototype will be mutated. 65 | * flat `boolean|string` sources properties are copied in a way to produce a *flat* target, the target's key 66 | is the full path (separated by '.') of the source's key, also if a string is provided it will be used as 67 | the path separator 68 | * unflat `boolean|string` it is the opposite of 'flat': assuming that the sources are in the *flat* format, 69 | it expands all flat properties -- whose name are path with '.' as the separator -- deeply into the target, 70 | also if a string is provided it will be used as the path separator 71 | * target `Object` the target of the extend, properties will be copied to this object 72 | * source1 `Object` the source of the extend, properties will be copied from this object 73 | * ... 74 | 75 | This is a full-featured *extend* of an object with one or more source object. 76 | 77 | It is easily translated from jQuery-like *extend()*: 78 | * `extend( target , source )` translate into `tree.extend( null , target , source )` 79 | * `extend( true , target , source )` translate into `tree.extend( { deep: true } , target , source )` 80 | 81 | However, here we have full control over what will be extended and how. 82 | 83 | **All the options above are inactive by default**. 84 | You can pass null as argument #0 to get the default behaviour (= all options are inactive). 85 | So using the default behaviour, `tree.extend()` will copy all enumerable properties, and perform a shallow copy (a nested object 86 | is not cloned, it remains a reference of the original one). 87 | 88 | With the *deep* option, a deep copy is performed, so nested object are cloned too. 89 | 90 | The *own* option clone only owned properties from the sources, properties that are part of the source's prototype would not 91 | be copied/cloned. 92 | 93 | The *nonEnum* option will clone properties that are not enumerable. 94 | 95 | The *descriptor* option will preserve property's descriptor, e.g. if the source property is not writable and not enumerable, 96 | so will be the copied property. 97 | 98 | In case of a *getter* properties: 99 | 100 | * without the *descriptor* option, the getter function of the source object will be called, the return value will be put 101 | into the target property (so it lose its getter/setter behaviour) 102 | * with the *descriptor* option, the getter & setter function of the source object will be copied (but not called) into the target 103 | property: the getter/setter behaviour is preserved 104 | 105 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it. 106 | We can see this *circular* feature in action in [this example](#example.circular). 107 | 108 | Mixing *inherit* and *deep* provides a nice multi-level inheritance. 109 | 110 | With the *flat* option example: 111 | ```js 112 | var o = { 113 | one: 1, 114 | sub: { 115 | two: 2, 116 | three: 3 117 | } 118 | } ; 119 | 120 | var flatCopy = tree.extend( { flat: true } , {} , o ) ; 121 | ``` 122 | ... it will produce: 123 | ```js 124 | { 125 | one: 1, 126 | "sub.two": 2, 127 | "sub.three": 3 128 | } 129 | ``` 130 | 131 | By the way, the *unflat* option does the opposite, and thus can reverse this back to the original form. 132 | 133 | The *deepFilter* option is used when you do not want to clone some type of object. 134 | Let's say you want a deep copy except for `Buffer` objects, you simply want them to share the same reference: 135 | ```js 136 | var o = { 137 | one: '1' , 138 | buf: new Buffer( "My buffer" ) , 139 | subtree: { 140 | two: 2 , 141 | three: 'THREE' 142 | } 143 | } ; 144 | 145 | // either 146 | var extended1 = tree.extend( { deep: true, deepFilter: { whitelist: [ Object.prototype ] } } , {} , o ) ; 147 | // or 148 | var extended2 = tree.extend( { deep: true, deepFilter: { blacklist: [ Buffer.prototype ] } } , {} , o ) ; 149 | ``` 150 | 151 | Doing this, we have `o.buf === extended1.buf === extended2.buf`, and `o.subtree !== extended1.subtree !== extended2.subtree`. 152 | 153 | 154 | 155 | 156 | ## .clone( original , [circular] ) 157 | 158 | * original `Object` the source object to clone 159 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected 160 | (referenced), if false then nested object are blindly cloned 161 | 162 | It returns a clone of the *original* object, providing the best object-cloning facility that this lib can offer. 163 | 164 | The clone produced are perfect independant copy **in 99% of use case**, but there is one big limitation: 165 | method that access variables in the parent's scope. 166 | 167 | The clone will share those variables with the *original* object, so they are not totally independant entity. 168 | Design pattern using closure to emulate *private member* (e.g. the revealing pattern) can cause trouble. 169 | 170 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it. 171 | 172 | 173 | Here is an example of this *circular* feature: 174 | ```js 175 | var o = { 176 | a: 'a', 177 | sub: { 178 | b: 'b' 179 | }, 180 | sub2: { 181 | c: 'c' 182 | } 183 | } ; 184 | 185 | o.loop = o ; 186 | o.sub.loop = o ; 187 | o.subcopy = o.sub ; 188 | o.sub.link = o.sub2 ; 189 | o.sub2.link = o.sub ; 190 | 191 | var c = tree.clone( o , true ) ; 192 | 193 | expect( c.loop ).to.be( c ) ; 194 | expect( c.sub ).to.be( c.subcopy ) ; 195 | expect( c.sub.loop ).to.be( c ) ; 196 | expect( c.subcopy.loop ).to.be( c ) ; 197 | expect( c.sub.link ).to.be( c.sub2 ) ; 198 | expect( c.sub2.link ).to.be( c.sub ) ; 199 | ``` 200 | 201 | ... without *circular* on, the `clone()` method would run forever, creating a new object independant nested object each time 202 | it reaches the *loop* property. 203 | We can see that the *subcopy* property remains a reference of *sub* even in the clone, thanks to the *circular* option. 204 | 205 | However, if we are sure that there isn't multiple reference to the same object or circular references, we can gain a lot of 206 | performances by leaving that options off. 207 | It can save a lot of `.indexOf()` call on big data structure. 208 | 209 | This method does not uses `extend()` anymore like in version 0.3.x, it now uses its own optimized code. 210 | However it is equivalent to an `extend()` with those options turned on: *deep, own, nonEnum, descriptor & proto*. 211 | If *circular* is on, it has the same effect than the `extend()`'s *circular* option. 212 | 213 | **Also please note that design pattern emulating private members using a closure's scope cannot be truly cloned** 214 | (e.g. the *revealing pattern*). 215 | This is not possible to mutate a function's scope. 216 | So the clone's methods will continue to inherit the parent's scope of the original function. 217 | 218 | 219 | 220 | 221 | ## .diff( left , right , [options] ) 222 | 223 | * left `Object` the left-hand side object structure 224 | * right `Object` the right-hand side object structure 225 | * options `Object` containing options, it supports: 226 | * path `string` the initial path, default: empty string 227 | * pathSeparator `string` the path separator, default: '.' 228 | 229 | This tool reports diff between a left-hand side and right-hand side object structure. 230 | It returns an object, each key is a path where a difference is reported, the value being an object containing (again) the path 231 | and a human-readable message. 232 | 233 | See this example: 234 | ```js 235 | var left = { 236 | a: 'a', 237 | b: 2, 238 | c: 'three', 239 | sub: { 240 | e: 5, 241 | f: 'six', 242 | } 243 | } ; 244 | 245 | var right = { 246 | b: 2, 247 | c: 3, 248 | d: 'dee', 249 | sub: { 250 | e: 5, 251 | f: 6, 252 | } 253 | } ; 254 | 255 | console.log( tree.diff( a , b ) ) ; 256 | ``` 257 | It will output: 258 | ```js 259 | { '.a': { path: '.a', message: 'does not exist in right-hand side' }, 260 | '.c': { path: '.c', message: 'different typeof: string - number' }, 261 | '.sub.f': { path: '.sub.f', message: 'different typeof: string - number' }, 262 | '.d': { path: '.d', message: 'does not exist in left-hand side' } } 263 | ``` 264 | 265 | -------------------------------------------------------------------------------- /lib/arrayLike.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | // Mimic Array's prototype methods, like Array#map(), Array#filter(), and so on... 32 | 33 | exports.map = function( object , fn ) { 34 | if ( ! object || typeof object !== 'object' ) { throw new Error( "Expecting an object" ) ; } 35 | return Object.fromEntries( Object.entries( object ).map( entry => [ entry[ 0 ] , fn( entry[ 1 ] ) ] ) ) ; 36 | } ; 37 | 38 | exports.filter = function( object , fn ) { 39 | if ( ! object || typeof object !== 'object' ) { throw new Error( "Expecting an object" ) ; } 40 | return Object.fromEntries( Object.entries( object ).filter( entry => fn( entry[ 1 ] ) ) ) ; 41 | } ; 42 | 43 | -------------------------------------------------------------------------------- /lib/browser.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | // Browser only get the essential of tree-kit, not the unfinished parts of it 32 | 33 | const tree = {} ; 34 | module.exports = tree ; 35 | 36 | 37 | 38 | tree.extend = require( './extend.js' ) ; 39 | tree.clone = require( './clone.js' ) ; 40 | tree.path = require( './path.js' ) ; 41 | tree.dotPath = require( './dotPath.js' ) ; 42 | tree.wildDotPath = require( './wildDotPath.js' ) ; 43 | 44 | Object.assign( tree , require( './arrayLike.js' ) ) ; 45 | 46 | -------------------------------------------------------------------------------- /lib/clone.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | /* 32 | Stand-alone fork of extend.js, without options. 33 | */ 34 | 35 | function clone( originalObject , circular ) { 36 | // First create an empty object with 37 | // same prototype of our original source 38 | 39 | var originalProto = Object.getPrototypeOf( originalObject ) ; 40 | 41 | // Opaque objects, like Date 42 | if ( clone.opaque.has( originalProto ) ) { return clone.opaque.get( originalProto )( originalObject ) ; } 43 | 44 | var propertyIndex , descriptor , keys , current , nextSource , proto , 45 | copies = [ { 46 | source: originalObject , 47 | target: Array.isArray( originalObject ) ? [] : Object.create( originalProto ) 48 | } ] , 49 | cloneObject = copies[ 0 ].target , 50 | refMap = new Map() ; 51 | 52 | refMap.set( originalObject , cloneObject ) ; 53 | 54 | 55 | // First in, first out 56 | while ( ( current = copies.shift() ) ) { 57 | keys = Object.getOwnPropertyNames( current.source ) ; 58 | 59 | for ( propertyIndex = 0 ; propertyIndex < keys.length ; propertyIndex ++ ) { 60 | // Save the source's descriptor 61 | descriptor = Object.getOwnPropertyDescriptor( current.source , keys[ propertyIndex ] ) ; 62 | 63 | 64 | if ( ! descriptor.value || typeof descriptor.value !== 'object' ) { 65 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ; 66 | continue ; 67 | } 68 | 69 | nextSource = descriptor.value ; 70 | 71 | if ( circular ) { 72 | if ( refMap.has( nextSource ) ) { 73 | // The source is already referenced, just assign reference 74 | descriptor.value = refMap.get( nextSource ) ; 75 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ; 76 | continue ; 77 | } 78 | } 79 | 80 | proto = Object.getPrototypeOf( descriptor.value ) ; 81 | 82 | // Opaque objects, like Date, not recursivity for them 83 | if ( clone.opaque.has( proto ) ) { 84 | descriptor.value = clone.opaque.get( proto )( descriptor.value ) ; 85 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ; 86 | continue ; 87 | } 88 | 89 | descriptor.value = Array.isArray( nextSource ) ? [] : Object.create( proto ) ; 90 | 91 | if ( circular ) { refMap.set( nextSource , descriptor.value ) ; } 92 | 93 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ; 94 | copies.push( { source: nextSource , target: descriptor.value } ) ; 95 | } 96 | } 97 | 98 | return cloneObject ; 99 | } 100 | 101 | module.exports = clone ; 102 | 103 | 104 | 105 | clone.opaque = new Map() ; 106 | clone.opaque.set( Date.prototype , src => new Date( src ) ) ; 107 | 108 | -------------------------------------------------------------------------------- /lib/diff.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | /* 32 | == Diff function == 33 | */ 34 | 35 | function diff( left , right , options ) { 36 | var i , key , keyPath , 37 | leftKeys , rightKeys , leftTypeof , rightTypeof , 38 | depth , diffObject , length , arrayMode ; 39 | 40 | leftTypeof = typeof left ; 41 | rightTypeof = typeof right ; 42 | 43 | if ( 44 | ! left || ( leftTypeof !== 'object' && leftTypeof !== 'function' ) || 45 | ! right || ( rightTypeof !== 'object' && rightTypeof !== 'function' ) 46 | ) { 47 | throw new Error( '[tree] diff() needs objects as argument #0 and #1' ) ; 48 | } 49 | 50 | if ( ! options || typeof options !== 'object' ) { options = {} ; } 51 | 52 | depth = options.depth || 0 ; 53 | 54 | // Things applied only for the root, not for recursive call 55 | if ( ! depth ) { 56 | options.diffObject = {} ; 57 | if ( ! options.path ) { options.path = '' ; } 58 | if ( ! options.pathSeparator ) { options.pathSeparator = '.' ; } 59 | } 60 | 61 | diffObject = options.diffObject ; 62 | 63 | 64 | // Left part 65 | if ( Array.isArray( left ) ) { 66 | arrayMode = true ; 67 | length = left.length ; 68 | } 69 | else { 70 | arrayMode = false ; 71 | leftKeys = Object.keys( left ) ; 72 | length = leftKeys.length ; 73 | } 74 | 75 | for ( i = 0 ; i < length ; i ++ ) { 76 | key = arrayMode ? i : leftKeys[ i ] ; 77 | keyPath = options.path + options.pathSeparator + key ; 78 | //console.log( 'L keyPath:' , keyPath ) ; 79 | 80 | if ( ! Object.hasOwn( right , key ) ) { 81 | diffObject[ keyPath ] = { path: keyPath , message: 'does not exist in right-hand side' } ; 82 | continue ; 83 | } 84 | 85 | leftTypeof = typeof left[ key ] ; 86 | rightTypeof = typeof right[ key ] ; 87 | 88 | if ( leftTypeof !== rightTypeof ) { 89 | diffObject[ keyPath ] = { path: keyPath , message: 'different typeof: ' + leftTypeof + ' - ' + rightTypeof } ; 90 | continue ; 91 | } 92 | 93 | if ( leftTypeof === 'object' || leftTypeof === 'function' ) { 94 | // Cleanup the 'null is an object' mess 95 | if ( ! left[ key ] ) { 96 | if ( right[ key ] ) { diffObject[ keyPath ] = { path: keyPath , message: 'different type: null - Object' } ; } 97 | continue ; 98 | } 99 | 100 | if ( ! right[ key ] ) { 101 | diffObject[ keyPath ] = { path: keyPath , message: 'different type: Object - null' } ; 102 | continue ; 103 | } 104 | 105 | if ( Array.isArray( left[ key ] ) && ! Array.isArray( right[ key ] ) ) { 106 | diffObject[ keyPath ] = { path: keyPath , message: 'different type: Array - Object' } ; 107 | continue ; 108 | } 109 | 110 | if ( ! Array.isArray( left[ key ] ) && Array.isArray( right[ key ] ) ) { 111 | diffObject[ keyPath ] = { path: keyPath , message: 'different type: Object - Array' } ; 112 | continue ; 113 | } 114 | 115 | diff( left[ key ] , right[ key ] , { 116 | path: keyPath , pathSeparator: options.pathSeparator , depth: depth + 1 , diffObject: diffObject 117 | } ) ; 118 | continue ; 119 | } 120 | 121 | if ( left[ key ] !== right[ key ] ) { 122 | diffObject[ keyPath ] = { path: keyPath , message: 'different value: ' + left[ key ] + ' - ' + right[ key ] } ; 123 | continue ; 124 | } 125 | } 126 | 127 | 128 | // Right part 129 | if ( Array.isArray( right ) ) { 130 | arrayMode = true ; 131 | length = right.length ; 132 | } 133 | else { 134 | arrayMode = false ; 135 | rightKeys = Object.keys( right ) ; 136 | length = rightKeys.length ; 137 | } 138 | 139 | for ( i = 0 ; i < length ; i ++ ) { 140 | key = arrayMode ? i : rightKeys[ i ] ; 141 | keyPath = options.path + options.pathSeparator + key ; 142 | //console.log( 'R keyPath:' , keyPath ) ; 143 | 144 | if ( ! Object.hasOwn( left , key ) ) { 145 | diffObject[ keyPath ] = { path: keyPath , message: 'does not exist in left-hand side' } ; 146 | continue ; 147 | } 148 | } 149 | 150 | return Object.keys( diffObject ).length ? diffObject : null ; 151 | } 152 | 153 | exports.diff = diff ; 154 | 155 | -------------------------------------------------------------------------------- /lib/dotPath.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | const dotPath = {} ; 32 | module.exports = dotPath ; 33 | 34 | 35 | 36 | const EMPTY_PATH = [] ; 37 | const PROTO_POLLUTION_MESSAGE = 'This would cause prototype pollution' ; 38 | 39 | 40 | 41 | function toPathArray( path ) { 42 | if ( Array.isArray( path ) ) { 43 | /* 44 | let i , iMax = path.length ; 45 | for ( i = 0 ; i < iMax ; i ++ ) { 46 | if ( typeof path[ i ] !== 'string' || typeof path[ i ] !== 'number' ) { path[ i ] = '' + path[ i ] ; } 47 | } 48 | //*/ 49 | return path ; 50 | } 51 | 52 | if ( ! path ) { return EMPTY_PATH ; } 53 | if ( typeof path === 'string' ) { 54 | return path[ path.length - 1 ] === '.' ? path.slice( 0 , - 1 ).split( '.' ) : path.split( '.' ) ; 55 | } 56 | 57 | throw new TypeError( '[tree.dotPath]: the path argument should be a string or an array' ) ; 58 | } 59 | 60 | // Expose toPathArray() 61 | dotPath.toPathArray = toPathArray ; 62 | 63 | 64 | 65 | // Walk the tree using the path array. 66 | function walk( object , pathArray , maxOffset = 0 ) { 67 | var index , indexMax , key , 68 | pointer = object ; 69 | 70 | for ( index = 0 , indexMax = pathArray.length + maxOffset ; index < indexMax ; index ++ ) { 71 | key = pathArray[ index ] ; 72 | 73 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 74 | if ( ! pointer || typeof pointer !== 'object' ) { return undefined ; } 75 | 76 | pointer = pointer[ key ] ; 77 | } 78 | 79 | return pointer ; 80 | } 81 | 82 | 83 | 84 | // Walk the tree, create missing element: pave the path up to before the last part of the path. 85 | // Return that before-the-last element. 86 | // Object MUST be an object! no check are performed for the first step! 87 | function pave( object , pathArray ) { 88 | var index , indexMax , key , 89 | pointer = object ; 90 | 91 | for ( index = 0 , indexMax = pathArray.length - 1 ; index < indexMax ; index ++ ) { 92 | key = pathArray[ index ] ; 93 | 94 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer[ key ] === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 95 | if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = {} ; } 96 | 97 | pointer = pointer[ key ] ; 98 | } 99 | 100 | return pointer ; 101 | } 102 | 103 | 104 | 105 | dotPath.get = ( object , path ) => walk( object , toPathArray( path ) ) ; 106 | 107 | 108 | 109 | dotPath.set = ( object , path , value ) => { 110 | if ( ! object || typeof object !== 'object' ) { return ; } 111 | 112 | var pathArray = toPathArray( path ) , 113 | key = pathArray[ pathArray.length - 1 ] ; 114 | 115 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 116 | 117 | var pointer = pave( object , pathArray ) ; 118 | 119 | pointer[ key ] = value ; 120 | 121 | return value ; 122 | } ; 123 | 124 | 125 | 126 | dotPath.define = ( object , path , value ) => { 127 | if ( ! object || typeof object !== 'object' ) { return ; } 128 | 129 | var pathArray = toPathArray( path ) , 130 | key = pathArray[ pathArray.length - 1 ] ; 131 | 132 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 133 | 134 | var pointer = pave( object , pathArray ) ; 135 | 136 | if ( ! ( key in pointer ) ) { pointer[ key ] = value ; } 137 | 138 | return pointer[ key ] ; 139 | } ; 140 | 141 | 142 | 143 | dotPath.inc = ( object , path ) => { 144 | if ( ! object || typeof object !== 'object' ) { return ; } 145 | 146 | var pathArray = toPathArray( path ) , 147 | key = pathArray[ pathArray.length - 1 ] ; 148 | 149 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 150 | 151 | var pointer = pave( object , pathArray ) ; 152 | 153 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] ++ ; } 154 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = 1 ; } 155 | 156 | return pointer[ key ] ; 157 | } ; 158 | 159 | 160 | 161 | dotPath.dec = ( object , path ) => { 162 | if ( ! object || typeof object !== 'object' ) { return ; } 163 | 164 | var pathArray = toPathArray( path ) , 165 | key = pathArray[ pathArray.length - 1 ] ; 166 | 167 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 168 | 169 | var pointer = pave( object , pathArray ) ; 170 | 171 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } 172 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } 173 | 174 | return pointer[ key ] ; 175 | } ; 176 | 177 | 178 | 179 | dotPath.concat = ( object , path , value ) => { 180 | if ( ! object || typeof object !== 'object' ) { return ; } 181 | 182 | var pathArray = toPathArray( path ) , 183 | key = pathArray[ pathArray.length - 1 ] ; 184 | 185 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 186 | 187 | var pointer = pave( object , pathArray ) ; 188 | 189 | if ( ! pointer[ key ] ) { pointer[ key ] = value ; } 190 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) { 191 | pointer[ key ] = pointer[ key ].concat( value ) ; 192 | } 193 | //else ? do nothing??? 194 | 195 | return pointer[ key ] ; 196 | } ; 197 | 198 | 199 | 200 | dotPath.insert = ( object , path , value ) => { 201 | if ( ! object || typeof object !== 'object' ) { return ; } 202 | 203 | var pathArray = toPathArray( path ) , 204 | key = pathArray[ pathArray.length - 1 ] ; 205 | 206 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 207 | 208 | var pointer = pave( object , pathArray ) ; 209 | 210 | if ( ! pointer[ key ] ) { pointer[ key ] = value ; } 211 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) { 212 | pointer[ key ] = value.concat( pointer[ key ] ) ; 213 | } 214 | //else ? do nothing??? 215 | 216 | return pointer[ key ] ; 217 | } ; 218 | 219 | 220 | 221 | dotPath.delete = ( object , path ) => { 222 | var pathArray = toPathArray( path ) , 223 | key = pathArray[ pathArray.length - 1 ] ; 224 | 225 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 226 | 227 | var pointer = walk( object , pathArray , - 1 ) ; 228 | 229 | if ( ! pointer || typeof pointer !== 'object' || ! Object.hasOwn( pointer , key ) ) { return false ; } 230 | 231 | delete pointer[ key ] ; 232 | 233 | return true ; 234 | } ; 235 | 236 | 237 | 238 | dotPath.autoPush = ( object , path , value ) => { 239 | if ( ! object || typeof object !== 'object' ) { return ; } 240 | 241 | var pathArray = toPathArray( path ) , 242 | key = pathArray[ pathArray.length - 1 ] ; 243 | 244 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 245 | 246 | var pointer = pave( object , pathArray ) ; 247 | 248 | if ( pointer[ key ] === undefined ) { pointer[ key ] = value ; } 249 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; } 250 | else { pointer[ key ] = [ pointer[ key ] , value ] ; } 251 | 252 | return pointer[ key ] ; 253 | } ; 254 | 255 | 256 | 257 | dotPath.append = ( object , path , value ) => { 258 | if ( ! object || typeof object !== 'object' ) { return ; } 259 | 260 | var pathArray = toPathArray( path ) , 261 | key = pathArray[ pathArray.length - 1 ] ; 262 | 263 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 264 | 265 | var pointer = pave( object , pathArray ) ; 266 | 267 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } 268 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; } 269 | //else ? do nothing??? 270 | 271 | return pointer[ key ] ; 272 | } ; 273 | 274 | 275 | 276 | dotPath.prepend = ( object , path , value ) => { 277 | if ( ! object || typeof object !== 'object' ) { return ; } 278 | 279 | var pathArray = toPathArray( path ) , 280 | key = pathArray[ pathArray.length - 1 ] ; 281 | 282 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 283 | 284 | var pointer = pave( object , pathArray ) ; 285 | 286 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } 287 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].unshift( value ) ; } 288 | //else ? do nothing??? 289 | 290 | return pointer[ key ] ; 291 | } ; 292 | 293 | -------------------------------------------------------------------------------- /lib/extend.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | /* 32 | == Extend function == 33 | */ 34 | 35 | /* 36 | options: 37 | * own: only copy own properties that are enumerable 38 | * nonEnum: copy non-enumerable properties as well, works only with own:true 39 | * descriptor: preserve property's descriptor 40 | * deep: boolean/Array/Set, if true perform a deep (recursive) extend, if it is an Array/Set of prototypes, only deep-copy 41 | objects of those prototypes 42 | (it is a replacement for deepFilter.whitelist which was removed in Tree Kit 0.6). 43 | * immutables: an Array/Set of immutable object's prototypes that are filtered out for deep-copy 44 | (it is a replacement for deepFilter.blacklist which was removed in Tree Kit 0.6). 45 | * maxDepth: used in conjunction with deep, when max depth is reached an exception is raised, default to 100 when 46 | the 'circular' option is off, or default to null if 'circular' is on 47 | * circular: boolean, circular references reconnection 48 | * move: boolean, move properties to target (delete properties from the sources) 49 | * preserve: boolean, existing properties in the target object are not overwritten 50 | * mask: boolean or number, reverse of 'preserve', only update existing properties in the target, do not create new keys, 51 | if its a number, the mask effect is only effective for the Nth element. 52 | E.g: .extend( {mask:2} , {} , object1 , object2 ) 53 | So object1 extends the empty object like, but object2 do not create new keys not present in object1. 54 | With mask:true or mask:1, the mask behavior would apply at step 1 too, when object1 would try to extend the empty object, 55 | and since an empty object has no key, nothing would change, and the whole extend would return an empty object. 56 | * nofunc: skip functions 57 | * deepFunc: in conjunction with 'deep', this will process sources functions like objects rather than 58 | copying/referencing them directly into the source, thus, the result will not be a function, it forces 'deep' 59 | * proto: try to clone objects with the right prototype, using Object.create() or mutating it with Object.setPrototypeOf(), 60 | it forces option 'own'. 61 | * inherit: rather than mutating target prototype for source prototype like the 'proto' option does, here it is 62 | the source itself that IS the prototype for the target. Force option 'own' and disable 'proto'. 63 | * skipRoot: the prototype of the target root object is NOT mutated only if this option is set. 64 | * flat: extend into the target top-level only, compose name with the path of the source, force 'deep', 65 | disable 'unflat', 'proto', 'inherit' 66 | * unflat: assume sources are in the 'flat' format, expand all properties deeply into the target, disable 'flat' 67 | */ 68 | function extend( options , target , ... sources ) { 69 | var i , source , newTarget = false , length = sources.length ; 70 | 71 | if ( ! length ) { return target ; } 72 | 73 | if ( ! options || typeof options !== 'object' ) { options = {} ; } 74 | 75 | var runtime = { depth: 0 , prefix: '' } ; 76 | 77 | if ( options.deep ) { 78 | if ( Array.isArray( options.deep ) ) { options.deep = new Set( options.deep ) ; } 79 | else if ( ! ( options.deep instanceof Set ) ) { options.deep = true ; } 80 | } 81 | 82 | if ( options.immutables ) { 83 | if ( Array.isArray( options.immutables ) ) { options.immutables = new Set( options.immutables ) ; } 84 | else if ( ! ( options.immutables instanceof Set ) ) { delete options.immutables ; } 85 | } 86 | 87 | if ( ! options.maxDepth && options.deep && ! options.circular ) { options.maxDepth = 100 ; } 88 | 89 | if ( options.deepFunc ) { options.deep = true ; } 90 | 91 | // 'flat' option force 'deep' 92 | if ( options.flat ) { 93 | options.deep = true ; 94 | options.proto = false ; 95 | options.inherit = false ; 96 | options.unflat = false ; 97 | if ( typeof options.flat !== 'string' ) { options.flat = '.' ; } 98 | } 99 | 100 | if ( options.unflat ) { 101 | options.deep = false ; 102 | options.proto = false ; 103 | options.inherit = false ; 104 | options.flat = false ; 105 | if ( typeof options.unflat !== 'string' ) { options.unflat = '.' ; } 106 | } 107 | 108 | // If the prototype is applied, only owned properties should be copied 109 | if ( options.inherit ) { options.own = true ; options.proto = false ; } 110 | else if ( options.proto ) { options.own = true ; } 111 | 112 | if ( ! target || ( typeof target !== 'object' && typeof target !== 'function' ) ) { 113 | newTarget = true ; 114 | } 115 | 116 | if ( ! options.skipRoot && ( options.inherit || options.proto ) ) { 117 | for ( i = length - 1 ; i >= 0 ; i -- ) { 118 | source = sources[ i ] ; 119 | if ( source && ( typeof source === 'object' || typeof source === 'function' ) ) { 120 | if ( options.inherit ) { 121 | if ( newTarget ) { target = Object.create( source ) ; } 122 | else { Object.setPrototypeOf( target , source ) ; } 123 | } 124 | else if ( options.proto ) { 125 | if ( newTarget ) { target = Object.create( Object.getPrototypeOf( source ) ) ; } 126 | else { Object.setPrototypeOf( target , Object.getPrototypeOf( source ) ) ; } 127 | } 128 | 129 | break ; 130 | } 131 | } 132 | } 133 | else if ( newTarget ) { 134 | target = {} ; 135 | } 136 | 137 | runtime.references = { sources: [] , targets: [] } ; 138 | 139 | for ( i = 0 ; i < length ; i ++ ) { 140 | source = sources[ i ] ; 141 | if ( ! source || ( typeof source !== 'object' && typeof source !== 'function' ) ) { continue ; } 142 | extendOne( runtime , options , target , source , options.mask <= i + 1 ) ; 143 | } 144 | 145 | return target ; 146 | } 147 | 148 | module.exports = extend ; 149 | 150 | 151 | 152 | function extendOne( runtime , options , target , source , mask ) { 153 | var sourceKeys , sourceKey ; 154 | 155 | // Max depth check 156 | if ( options.maxDepth && runtime.depth > options.maxDepth ) { 157 | throw new Error( '[tree] extend(): max depth reached(' + options.maxDepth + ')' ) ; 158 | } 159 | 160 | 161 | if ( options.circular ) { 162 | runtime.references.sources.push( source ) ; 163 | runtime.references.targets.push( target ) ; 164 | } 165 | 166 | // 'unflat' mode computing 167 | if ( options.unflat && runtime.depth === 0 ) { 168 | for ( sourceKey in source ) { 169 | runtime.unflatKeys = sourceKey.split( options.unflat ) ; 170 | runtime.unflatIndex = 0 ; 171 | runtime.unflatFullKey = sourceKey ; 172 | extendOneKV( runtime , options , target , source , runtime.unflatKeys[ runtime.unflatIndex ] , mask ) ; 173 | } 174 | 175 | delete runtime.unflatKeys ; 176 | delete runtime.unflatIndex ; 177 | delete runtime.unflatFullKey ; 178 | } 179 | else if ( options.own ) { 180 | if ( options.nonEnum ) { sourceKeys = Object.getOwnPropertyNames( source ) ; } 181 | else { sourceKeys = Object.keys( source ) ; } 182 | 183 | for ( sourceKey of sourceKeys ) { 184 | extendOneKV( runtime , options , target , source , sourceKey , mask ) ; 185 | } 186 | } 187 | else { 188 | for ( sourceKey in source ) { 189 | extendOneKV( runtime , options , target , source , sourceKey , mask ) ; 190 | } 191 | } 192 | } 193 | 194 | 195 | 196 | function extendOneKV( runtime , options , target , source , sourceKey , mask ) { 197 | // OMG, this DEPRECATED __proto__ shit is still alive and can be used to hack anything >< 198 | if ( sourceKey === '__proto__' ) { return ; } 199 | 200 | let sourceValue , sourceDescriptor , sourceValueProto ; 201 | 202 | if ( runtime.unflatKeys ) { 203 | if ( runtime.unflatIndex < runtime.unflatKeys.length - 1 ) { 204 | sourceValue = {} ; 205 | } 206 | else { 207 | sourceValue = source[ runtime.unflatFullKey ] ; 208 | } 209 | } 210 | else if ( options.descriptor ) { 211 | // If descriptor is on, get it now 212 | sourceDescriptor = Object.getOwnPropertyDescriptor( source , sourceKey ) ; 213 | sourceValue = sourceDescriptor.value ; 214 | } 215 | else { 216 | // We have to trigger an eventual getter only once 217 | sourceValue = source[ sourceKey ] ; 218 | } 219 | 220 | let targetKey = runtime.prefix + sourceKey ; 221 | 222 | // Do not copy if property is a function and we don't want them 223 | if ( options.nofunc && typeof sourceValue === 'function' ) { return ; } 224 | 225 | // Again, trigger an eventual getter only once 226 | let targetValue = target[ targetKey ] ; 227 | let targetValueIsObject = targetValue && ( typeof targetValue === 'object' || typeof targetValue === 'function' ) ; 228 | let sourceValueIsObject = sourceValue && ( typeof sourceValue === 'object' || typeof sourceValue === 'function' ) ; 229 | 230 | if ( 231 | ( options.deep || runtime.unflatKeys ) 232 | && sourceValue 233 | && ( typeof sourceValue === 'object' || ( options.deepFunc && typeof sourceValue === 'function' ) ) 234 | && ( ! options.descriptor || ! sourceDescriptor.get ) 235 | // not a condition we just cache sourceValueProto now... ok it's trashy >< 236 | && ( ( sourceValueProto = Object.getPrototypeOf( sourceValue ) ) || true ) 237 | && ( ! ( options.deep instanceof Set ) || options.deep.has( sourceValueProto ) ) 238 | && ( ! options.immutables || ! options.immutables.has( sourceValueProto ) ) 239 | && ( ! options.preserve || targetValueIsObject ) 240 | && ( ! mask || targetValueIsObject ) 241 | ) { 242 | let indexOfSource = options.circular ? runtime.references.sources.indexOf( sourceValue ) : - 1 ; 243 | 244 | if ( options.flat ) { 245 | // No circular references reconnection when in 'flat' mode 246 | if ( indexOfSource >= 0 ) { return ; } 247 | 248 | extendOne( 249 | { 250 | depth: runtime.depth + 1 , 251 | prefix: runtime.prefix + sourceKey + options.flat , 252 | references: runtime.references 253 | } , 254 | options , target , sourceValue , mask 255 | ) ; 256 | } 257 | else { 258 | if ( indexOfSource >= 0 ) { 259 | // Circular references reconnection... 260 | targetValue = runtime.references.targets[ indexOfSource ] ; 261 | 262 | if ( options.descriptor ) { 263 | Object.defineProperty( target , targetKey , { 264 | value: targetValue , 265 | enumerable: sourceDescriptor.enumerable , 266 | writable: sourceDescriptor.writable , 267 | configurable: sourceDescriptor.configurable 268 | } ) ; 269 | } 270 | else { 271 | target[ targetKey ] = targetValue ; 272 | } 273 | 274 | return ; 275 | } 276 | 277 | if ( ! targetValueIsObject || ! Object.hasOwn( target , targetKey ) ) { 278 | if ( Array.isArray( sourceValue ) ) { targetValue = [] ; } 279 | else if ( options.proto ) { targetValue = Object.create( sourceValueProto ) ; } 280 | else if ( options.inherit ) { targetValue = Object.create( sourceValue ) ; } 281 | else { targetValue = {} ; } 282 | 283 | if ( options.descriptor ) { 284 | Object.defineProperty( target , targetKey , { 285 | value: targetValue , 286 | enumerable: sourceDescriptor.enumerable , 287 | writable: sourceDescriptor.writable , 288 | configurable: sourceDescriptor.configurable 289 | } ) ; 290 | } 291 | else { 292 | target[ targetKey ] = targetValue ; 293 | } 294 | } 295 | else if ( options.proto && Object.getPrototypeOf( targetValue ) !== sourceValueProto ) { 296 | Object.setPrototypeOf( targetValue , sourceValueProto ) ; 297 | } 298 | else if ( options.inherit && Object.getPrototypeOf( targetValue ) !== sourceValue ) { 299 | Object.setPrototypeOf( targetValue , sourceValue ) ; 300 | } 301 | 302 | if ( options.circular ) { 303 | runtime.references.sources.push( sourceValue ) ; 304 | runtime.references.targets.push( targetValue ) ; 305 | } 306 | 307 | if ( runtime.unflatKeys && runtime.unflatIndex < runtime.unflatKeys.length - 1 ) { 308 | // Finish unflatting this property 309 | let nextSourceKey = runtime.unflatKeys[ runtime.unflatIndex + 1 ] ; 310 | 311 | extendOneKV( 312 | { 313 | depth: runtime.depth , // keep the same depth 314 | unflatKeys: runtime.unflatKeys , 315 | unflatIndex: runtime.unflatIndex + 1 , 316 | unflatFullKey: runtime.unflatFullKey , 317 | prefix: '' , 318 | references: runtime.references 319 | } , 320 | options , targetValue , source , nextSourceKey , mask 321 | ) ; 322 | } 323 | else { 324 | // Recursively extends sub-object 325 | extendOne( 326 | { 327 | depth: runtime.depth + 1 , 328 | prefix: '' , 329 | references: runtime.references 330 | } , 331 | options , targetValue , sourceValue , mask 332 | ) ; 333 | } 334 | } 335 | } 336 | else if ( mask && ( targetValue === undefined || targetValueIsObject || sourceValueIsObject ) ) { 337 | // Do not create new value, and so do not delete source's properties that were not moved. 338 | // We also do not overwrite object with non-object, and we don't overwrite non-object with object (preserve hierarchy) 339 | return ; 340 | } 341 | else if ( options.preserve && targetValue !== undefined ) { 342 | // Do not overwrite, and so do not delete source's properties that were not moved 343 | return ; 344 | } 345 | else if ( ! options.inherit ) { 346 | if ( options.descriptor ) { Object.defineProperty( target , targetKey , sourceDescriptor ) ; } 347 | else { target[ targetKey ] = targetValue = sourceValue ; } 348 | } 349 | 350 | // Delete owned property of the source object 351 | if ( options.move ) { delete source[ sourceKey ] ; } 352 | } 353 | 354 | -------------------------------------------------------------------------------- /lib/lazy.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | /* 32 | DEPRECATED, moved to its own module: lazyness 33 | */ 34 | 35 | exports.defineLazyProperty = function defineLazyProperty( object , name , func ) { 36 | Object.defineProperty( object , name , { 37 | configurable: true , 38 | enumerable: true , 39 | get: function() { 40 | 41 | var value = func() ; 42 | 43 | Object.defineProperty( object , name , { 44 | configurable: true , 45 | enumerable: true , 46 | writable: false , 47 | value: value 48 | } ) ; 49 | 50 | return value ; 51 | } 52 | } ) ; 53 | } ; 54 | 55 | -------------------------------------------------------------------------------- /lib/mask.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | const tree = require( './tree.js' ) ; 32 | const util = require( 'util' ) ; 33 | 34 | 35 | 36 | // Create and export 37 | const masklib = {} ; 38 | module.exports = masklib ; 39 | 40 | 41 | 42 | /* 43 | == Mask-family class == 44 | 45 | Recursively select values in the input object if the same path in the mask object is set. 46 | */ 47 | 48 | /* 49 | TODO: 50 | - negative mask 51 | - constraint check 52 | - Maskable object, like in csk-php 53 | */ 54 | 55 | masklib.Mask = function Mask() { 56 | throw new Error( 'Cannot create a tree.Mask() directly' ) ; 57 | } ; 58 | 59 | 60 | 61 | const maskDefaultOptions = { 62 | clone: false , 63 | path: '' , 64 | pathSeparator: '.' 65 | } ; 66 | 67 | 68 | 69 | /* 70 | options: 71 | clone: the output clone the input rather than reference it 72 | pathSeperator: when expressing path, this is the separator 73 | leaf: a callback to exec for each mask leaf 74 | node? a callback to exec for each mask node 75 | */ 76 | masklib.createMask = function createMask( maskArgument , options ) { 77 | if ( maskArgument === null || typeof maskArgument !== 'object' ) { 78 | throw new TypeError( '[tree] .createMask() : Argument #1 should be an object' ) ; 79 | } 80 | 81 | if ( options !== null && typeof options === 'object' ) { options = tree.extend( null , {} , maskDefaultOptions , options ) ; } 82 | else { options = maskDefaultOptions ; } 83 | 84 | var mask = Object.create( masklib.Mask.prototype , { 85 | __options__: { value: options , writable: true } 86 | } ) ; 87 | 88 | tree.extend( null , mask , maskArgument ) ; 89 | 90 | return mask ; 91 | } ; 92 | 93 | 94 | 95 | // Apply the mask to an input tree 96 | masklib.Mask.prototype.applyTo = function applyTo( input , context , contextOverideDefault ) { 97 | // Arguments checking 98 | if ( input === null || typeof input !== 'object' ) { 99 | throw new TypeError( '[tree] .applyTo() : Argument #1 should be an object' ) ; 100 | } 101 | 102 | if ( contextOverideDefault ) { 103 | context = tree.extend( null , 104 | { 105 | mask: this , 106 | options: this.__options__ , 107 | path: this.__options__.path 108 | } , 109 | context 110 | ) ; 111 | } 112 | else if ( context === undefined ) { 113 | context = { 114 | mask: this , 115 | options: this.__options__ , 116 | path: this.__options__.path 117 | } ; 118 | } 119 | 120 | 121 | // Init 122 | //console.log( context ) ; 123 | var result , nextPath , output , 124 | i , key , maskValue , 125 | maskKeyList = Object.keys( context.mask ) , 126 | j , inputKey , inputValue , inputKeyList ; 127 | 128 | if ( Array.isArray( input ) ) { output = [] ; } 129 | else { output = {} ; } 130 | 131 | 132 | // Iterate through mask properties 133 | for ( i = 0 ; i < maskKeyList.length ; i ++ ) { 134 | key = maskKeyList[ i ] ; 135 | maskValue = context.mask[ key ] ; 136 | 137 | //console.log( '\nnext loop: ' , key , maskValue ) ; 138 | 139 | // The special key * is a wildcard, it match everything 140 | if ( key === '*' ) { 141 | //console.log( 'wildcard' ) ; 142 | inputKeyList = Object.keys( input ) ; 143 | 144 | for ( j = 0 ; j < inputKeyList.length ; j ++ ) { 145 | inputKey = inputKeyList[ j ] ; 146 | inputValue = input[ inputKey ] ; 147 | 148 | //console.log( '*: ' , inputKey ) ; 149 | nextPath = context.path + context.options.pathSeparator + inputKey ; 150 | 151 | // If it is an array or object, recursively check it 152 | if ( maskValue !== null && typeof maskValue === 'object' ) { 153 | if ( inputValue !== null && typeof inputValue === 'object' ) { 154 | if ( inputValue instanceof masklib.Mask ) { 155 | output[ inputKey ] = inputValue.applyTo( inputValue , { path: nextPath } , true ) ; 156 | } 157 | else { 158 | output[ inputKey ] = this.applyTo( inputValue , tree.extend( null , {} , context , { mask: maskValue , path: nextPath } ) ) ; 159 | } 160 | } 161 | else if ( typeof context.options.leaf === 'function' ) { 162 | output[ inputKey ] = this.applyTo( {} , tree.extend( null , {} , context , { mask: maskValue , path: nextPath } ) ) ; 163 | } 164 | } 165 | else if ( maskValue !== null && typeof context.options.leaf === 'function' ) { 166 | //console.log( 'leaf callback' ) ; 167 | result = context.options.leaf( input , inputKey , maskValue , nextPath ) ; 168 | if ( ! ( result instanceof Error ) ) { output[ inputKey ] = result ; } 169 | } 170 | else if ( context.options.clone && ( inputValue !== null && typeof inputValue === 'object' ) ) { 171 | output[ inputKey ] = tree.extend( { deep: true } , {} , inputValue ) ; 172 | } 173 | else { 174 | output[ inputKey ] = inputValue ; 175 | } 176 | } 177 | 178 | continue ; 179 | } 180 | 181 | 182 | nextPath = context.path + context.options.pathSeparator + key ; 183 | 184 | // If it is an object, recursively check it 185 | //if ( maskValue instanceof masklib.Mask ) 186 | if ( maskValue !== null && typeof maskValue === 'object' ) { 187 | //console.log( 'sub' ) ; 188 | 189 | if ( Object.hasOwn( input , key ) && input[ key ] !== null && typeof input[ key ] === 'object' ) { 190 | //console.log( 'recursive call' ) ; 191 | 192 | if ( input.key instanceof masklib.Mask ) { 193 | output[ key ] = input.key.applyTo( input[ key ] , { path: nextPath } , true ) ; 194 | } 195 | else { 196 | output[ key ] = this.applyTo( input[ key ] , tree.extend( null , {} , context , { mask: maskValue , path: nextPath } ) ) ; 197 | } 198 | } 199 | // recursive call only if there are callback 200 | else if ( context.options.leaf ) { 201 | //console.log( 'recursive call' ) ; 202 | output[ key ] = this.applyTo( {} , tree.extend( null , {} , context , { mask: maskValue , path: nextPath } ) ) ; 203 | } 204 | } 205 | // If mask exists, add the key 206 | else if ( Object.hasOwn( input , key ) ) { 207 | //console.log( 'property found' ) ; 208 | 209 | if ( maskValue !== undefined && typeof context.options.leaf === 'function' ) { 210 | //console.log( 'leaf callback' ) ; 211 | result = context.options.leaf( input , key , maskValue , nextPath ) ; 212 | if ( ! ( result instanceof Error ) ) { output[ key ] = result ; } 213 | } 214 | else if ( context.options.clone && ( input[ key ] !== null && typeof input[ key ] === 'object' ) ) { 215 | output[ key ] = tree.extend( { deep: true } , {} , input[ key ] ) ; 216 | } 217 | else { 218 | output[ key ] = input[ key ] ; 219 | } 220 | } 221 | else if ( maskValue !== undefined && typeof context.options.leaf === 'function' ) { 222 | //console.log( 'leaf callback' ) ; 223 | result = context.options.leaf( input , key , maskValue , nextPath ) ; 224 | if ( ! ( result instanceof Error ) ) { output[ key ] = result ; } 225 | } 226 | } 227 | 228 | return output ; 229 | } ; 230 | 231 | 232 | 233 | // InverseMask: create an output tree from the input, by excluding properties of the mask 234 | 235 | masklib.InverseMask = function InverseMask() { 236 | throw new Error( 'Cannot create a tree.InverseMask() directly' ) ; 237 | } ; 238 | 239 | util.inherits( masklib.InverseMask , masklib.Mask ) ; 240 | 241 | 242 | 243 | /* 244 | options: 245 | clone: the output clone the input rather than reference it 246 | pathSeperator: when expressing path, this is the separator 247 | */ 248 | masklib.createInverseMask = function createInverseMask( maskArgument , options ) { 249 | if ( maskArgument === null || typeof maskArgument !== 'object' ) { 250 | throw new TypeError( '[tree] .createInverseMask() : Argument #1 should be an object' ) ; 251 | } 252 | 253 | if ( options !== null && typeof options === 'object' ) { options = tree.extend( null , {} , maskDefaultOptions , options ) ; } 254 | else { options = maskDefaultOptions ; } 255 | 256 | var mask = Object.create( masklib.InverseMask.prototype , { 257 | __options__: { value: options , writable: true } 258 | } ) ; 259 | 260 | tree.extend( null , mask , maskArgument ) ; 261 | 262 | return mask ; 263 | } ; 264 | 265 | 266 | 267 | // Apply the mask to an input tree 268 | masklib.InverseMask.prototype.applyTo = function applyTo( input , context , contextOverideDefault ) { 269 | // Arguments checking 270 | if ( input === null || typeof input !== 'object' ) { 271 | throw new TypeError( '[tree] .applyTo() : Argument #1 should be an object' ) ; 272 | } 273 | 274 | if ( contextOverideDefault ) { 275 | context = tree.extend( null , 276 | { 277 | mask: this , 278 | options: this.__options__ , 279 | path: this.__options__.path 280 | } , 281 | context 282 | ) ; 283 | } 284 | else if ( context === undefined ) { 285 | context = { 286 | mask: this , 287 | options: this.__options__ , 288 | path: this.__options__.path 289 | } ; 290 | } 291 | 292 | 293 | // Init 294 | //console.log( context ) ; 295 | var nextPath , output , 296 | i , key , maskValue , 297 | maskKeyList = Object.keys( context.mask ) , 298 | j , inputKey , inputValue , inputKeyList ; 299 | 300 | if ( Array.isArray( input ) ) { output = tree.extend( { deep: true } , [] , input ) ; } 301 | else { output = tree.extend( { deep: true } , {} , input ) ; } 302 | 303 | //console.log( output ) ; 304 | 305 | // Iterate through mask properties 306 | for ( i = 0 ; i < maskKeyList.length ; i ++ ) { 307 | key = maskKeyList[ i ] ; 308 | maskValue = context.mask[ key ] ; 309 | 310 | //console.log( '\nnext loop: ' , key , maskValue ) ; 311 | 312 | // The special key * is a wildcard, it match everything 313 | if ( key === '*' ) { 314 | //console.log( 'wildcard' ) ; 315 | inputKeyList = Object.keys( input ) ; 316 | 317 | for ( j = 0 ; j < inputKeyList.length ; j ++ ) { 318 | inputKey = inputKeyList[ j ] ; 319 | inputValue = input[ inputKey ] ; 320 | 321 | //console.log( '*: ' , inputKey ) ; 322 | nextPath = context.path + context.options.pathSeparator + inputKey ; 323 | 324 | // If it is an array or object, recursively check it 325 | if ( maskValue !== null && typeof maskValue === 'object' ) { 326 | if ( inputValue !== null && typeof inputValue === 'object' ) { 327 | if ( inputValue instanceof masklib.Mask ) { 328 | output[ inputKey ] = inputValue.applyTo( inputValue , { path: nextPath } , true ) ; 329 | } 330 | else { 331 | output[ inputKey ] = this.applyTo( inputValue , tree.extend( null , {} , context , { mask: maskValue , path: nextPath } ) ) ; 332 | } 333 | } 334 | } 335 | else { 336 | delete output[ inputKey ] ; 337 | } 338 | } 339 | 340 | continue ; 341 | } 342 | 343 | 344 | nextPath = context.path + context.options.pathSeparator + key ; 345 | 346 | // If it is an object, recursively check it 347 | //if ( maskValue instanceof masklib.Mask ) 348 | if ( maskValue !== null && typeof maskValue === 'object' ) { 349 | //console.log( 'sub' ) ; 350 | 351 | if ( Object.hasOwn( input , key ) && input[ key ] !== null && typeof input[ key ] === 'object' ) { 352 | //console.log( 'recursive call' ) ; 353 | 354 | if ( input.key instanceof masklib.Mask ) { 355 | output[ key ] = input.key.applyTo( input[ key ] , { path: nextPath } , true ) ; 356 | } 357 | else { 358 | output[ key ] = this.applyTo( input[ key ] , tree.extend( null , {} , context , { mask: maskValue , path: nextPath } ) ) ; 359 | } 360 | } 361 | } 362 | // If mask exists, remove the key 363 | else if ( Object.hasOwn( input , key ) ) { 364 | delete output[ key ] ; 365 | } 366 | } 367 | 368 | return output ; 369 | } ; 370 | 371 | -------------------------------------------------------------------------------- /lib/path.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | const treePath = {} ; 32 | module.exports = treePath ; 33 | 34 | 35 | 36 | const PROTO_POLLUTION_MESSAGE = 'This would cause prototype pollution' ; 37 | 38 | 39 | 40 | treePath.op = function( type , object , path , value ) { 41 | var i , parts , last , pointer , key , isArray = false , pathArrayMode = false , isGenericSet , canBeEmpty = true ; 42 | 43 | if ( ! object || typeof object !== 'object' ) { return ; } 44 | 45 | if ( typeof path === 'string' ) { 46 | // Split the path into parts 47 | if ( path ) { parts = path.match( /([.#[\]]|[^.#[\]]+)/g ) ; } 48 | else { parts = [ '' ] ; } 49 | 50 | if ( parts[ 0 ] === '.' ) { parts.unshift( '' ) ; } 51 | if ( parts[ parts.length - 1 ] === '.' ) { parts.push( '' ) ; } 52 | } 53 | else if ( Array.isArray( path ) ) { 54 | parts = path ; 55 | pathArrayMode = true ; 56 | /* 57 | for ( i = 0 ; i < parts.length ; i ++ ) { 58 | if ( typeof parts[ i ] !== 'string' || typeof parts[ i ] !== 'number' ) { parts[ i ] = '' + parts[ i ] ; } 59 | } 60 | //*/ 61 | } 62 | else { 63 | throw new TypeError( '[tree.path] .' + type + '(): the path argument should be a string or an array' ) ; 64 | } 65 | 66 | switch ( type ) { 67 | case 'get' : 68 | case 'delete' : 69 | isGenericSet = false ; 70 | break ; 71 | case 'set' : 72 | case 'define' : 73 | case 'inc' : 74 | case 'dec' : 75 | case 'append' : 76 | case 'prepend' : 77 | case 'concat' : 78 | case 'insert' : 79 | case 'autoPush' : 80 | isGenericSet = true ; 81 | break ; 82 | default : 83 | throw new TypeError( "[tree.path] .op(): wrong type of operation '" + type + "'" ) ; 84 | } 85 | 86 | //console.log( parts ) ; 87 | // The pointer start at the object's root 88 | pointer = object ; 89 | 90 | last = parts.length - 1 ; 91 | 92 | for ( i = 0 ; i <= last ; i ++ ) { 93 | if ( pathArrayMode ) { 94 | if ( key === undefined ) { 95 | key = parts[ i ] ; 96 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 97 | continue ; 98 | } 99 | 100 | if ( typeof pointer[ key ] === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 101 | if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { 102 | if ( ! isGenericSet ) { return undefined ; } 103 | pointer[ key ] = {} ; 104 | } 105 | 106 | pointer = pointer[ key ] ; 107 | key = parts[ i ] ; 108 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 109 | continue ; 110 | } 111 | else if ( parts[ i ] === '.' ) { 112 | isArray = false ; 113 | 114 | if ( key === undefined ) { 115 | if ( ! canBeEmpty ) { 116 | canBeEmpty = true ; 117 | continue ; 118 | } 119 | 120 | key = '' ; 121 | } 122 | 123 | if ( typeof pointer[ key ] === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 124 | if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { 125 | if ( ! isGenericSet ) { return undefined ; } 126 | pointer[ key ] = {} ; 127 | } 128 | 129 | pointer = pointer[ key ] ; 130 | canBeEmpty = true ; 131 | 132 | continue ; 133 | } 134 | else if ( parts[ i ] === '#' || parts[ i ] === '[' ) { 135 | isArray = true ; 136 | canBeEmpty = false ; 137 | 138 | if ( key === undefined ) { 139 | // The root element cannot be altered, we are in trouble if an array is expected but we have only a regular object. 140 | if ( ! Array.isArray( pointer ) ) { return undefined ; } 141 | continue ; 142 | } 143 | 144 | if ( typeof pointer[ key ] === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 145 | if ( ! pointer[ key ] || ! Array.isArray( pointer[ key ] ) ) { 146 | if ( ! isGenericSet ) { return undefined ; } 147 | pointer[ key ] = [] ; 148 | } 149 | 150 | pointer = pointer[ key ] ; 151 | 152 | continue ; 153 | } 154 | else if ( parts[ i ] === ']' ) { 155 | // Closing bracket: do nothing 156 | canBeEmpty = false ; 157 | continue ; 158 | } 159 | 160 | canBeEmpty = false ; 161 | 162 | if ( ! isArray ) { 163 | key = parts[ i ] ; 164 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 165 | continue ; 166 | } 167 | 168 | switch ( parts[ i ] ) { 169 | case 'length' : 170 | key = 'length' ; 171 | break ; 172 | 173 | // Pseudo-key 174 | case 'first' : 175 | key = 0 ; 176 | break ; 177 | case 'last' : 178 | key = pointer.length - 1 ; 179 | if ( key < 0 ) { key = 0 ; } 180 | break ; 181 | case 'next' : 182 | if ( ! isGenericSet ) { return undefined ; } 183 | key = pointer.length ; 184 | break ; 185 | case 'insert' : 186 | if ( ! isGenericSet ) { return undefined ; } 187 | pointer.unshift( undefined ) ; 188 | key = 0 ; 189 | break ; 190 | 191 | // default = number 192 | default : 193 | // Convert the string key to a numerical index 194 | key = parseInt( parts[ i ] , 10 ) ; 195 | } 196 | } 197 | 198 | switch ( type ) { 199 | case 'get' : 200 | return pointer[ key ] ; 201 | case 'delete' : 202 | if ( isArray && typeof key === 'number' ) { pointer.splice( key , 1 ) ; } 203 | else { delete pointer[ key ] ; } 204 | return ; 205 | case 'set' : 206 | pointer[ key ] = value ; 207 | return pointer[ key ] ; 208 | case 'define' : 209 | // define: set only if it doesn't exist 210 | if ( ! ( key in pointer ) ) { pointer[ key ] = value ; } 211 | return pointer[ key ] ; 212 | case 'inc' : 213 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] ++ ; } 214 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = 1 ; } 215 | return pointer[ key ] ; 216 | case 'dec' : 217 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } 218 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } 219 | return pointer[ key ] ; 220 | case 'append' : 221 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } 222 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; } 223 | //else ? do nothing??? 224 | return pointer[ key ] ; 225 | case 'prepend' : 226 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } 227 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].unshift( value ) ; } 228 | //else ? do nothing??? 229 | return pointer[ key ] ; 230 | case 'concat' : 231 | if ( ! pointer[ key ] ) { pointer[ key ] = value ; } 232 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) { 233 | pointer[ key ] = pointer[ key ].concat( value ) ; 234 | } 235 | //else ? do nothing??? 236 | return pointer[ key ] ; 237 | case 'insert' : 238 | if ( ! pointer[ key ] ) { pointer[ key ] = value ; } 239 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) { 240 | pointer[ key ] = value.concat( pointer[ key ] ) ; 241 | } 242 | //else ? do nothing??? 243 | return pointer[ key ] ; 244 | case 'autoPush' : 245 | if ( pointer[ key ] === undefined ) { pointer[ key ] = value ; } 246 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; } 247 | else { pointer[ key ] = [ pointer[ key ] , value ] ; } 248 | return pointer[ key ] ; 249 | } 250 | } ; 251 | 252 | 253 | 254 | // get, set and delete use the same op() function 255 | treePath.get = treePath.op.bind( undefined , 'get' ) ; 256 | treePath.delete = treePath.op.bind( undefined , 'delete' ) ; 257 | treePath.set = treePath.op.bind( undefined , 'set' ) ; 258 | treePath.define = treePath.op.bind( undefined , 'define' ) ; 259 | treePath.inc = treePath.op.bind( undefined , 'inc' ) ; 260 | treePath.dec = treePath.op.bind( undefined , 'dec' ) ; 261 | treePath.append = treePath.op.bind( undefined , 'append' ) ; 262 | treePath.prepend = treePath.op.bind( undefined , 'prepend' ) ; 263 | treePath.concat = treePath.op.bind( undefined , 'concat' ) ; 264 | treePath.insert = treePath.op.bind( undefined , 'insert' ) ; 265 | treePath.autoPush = treePath.op.bind( undefined , 'autoPush' ) ; 266 | 267 | 268 | 269 | // Prototype used for object creation, so they can be created with Object.create( tree.path.prototype ) 270 | treePath.prototype = { 271 | get: function( path ) { return treePath.get( this , path ) ; } , 272 | delete: function( path ) { return treePath.delete( this , path ) ; } , 273 | set: function( path , value ) { return treePath.set( this , path , value ) ; } , 274 | define: function( path , value ) { return treePath.define( this , path , value ) ; } , 275 | inc: function( path , value ) { return treePath.inc( this , path , value ) ; } , 276 | dec: function( path , value ) { return treePath.dec( this , path , value ) ; } , 277 | append: function( path , value ) { return treePath.append( this , path , value ) ; } , 278 | prepend: function( path , value ) { return treePath.prepend( this , path , value ) ; } , 279 | concat: function( path , value ) { return treePath.concat( this , path , value ) ; } , 280 | insert: function( path , value ) { return treePath.insert( this , path , value ) ; } , 281 | autoPush: function( path , value ) { return treePath.autoPush( this , path , value ) ; } 282 | } ; 283 | 284 | 285 | 286 | // Upgrade an object so it can support get, set and delete at its root 287 | treePath.upgrade = function( object ) { 288 | Object.defineProperties( object , { 289 | get: { value: treePath.op.bind( undefined , 'get' , object ) } , 290 | delete: { value: treePath.op.bind( undefined , 'delete' , object ) } , 291 | set: { value: treePath.op.bind( undefined , 'set' , object ) } , 292 | define: { value: treePath.op.bind( undefined , 'define' , object ) } , 293 | inc: { value: treePath.op.bind( undefined , 'inc' , object ) } , 294 | dec: { value: treePath.op.bind( undefined , 'dec' , object ) } , 295 | append: { value: treePath.op.bind( undefined , 'append' , object ) } , 296 | prepend: { value: treePath.op.bind( undefined , 'prepend' , object ) } , 297 | concat: { value: treePath.op.bind( undefined , 'concat' , object ) } , 298 | insert: { value: treePath.op.bind( undefined , 'insert' , object ) } , 299 | autoPush: { value: treePath.op.bind( undefined , 'autoPush' , object ) } 300 | } ) ; 301 | } ; 302 | 303 | -------------------------------------------------------------------------------- /lib/tree.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | const tree = {} ; 32 | module.exports = tree ; 33 | 34 | 35 | 36 | Object.assign( tree , 37 | { 38 | extend: require( './extend.js' ) , 39 | clone: require( './clone.js' ) , 40 | path: require( './path.js' ) , 41 | dotPath: require( './dotPath.js' ) , 42 | wildDotPath: require( './wildDotPath.js' ) 43 | } , 44 | require( './lazy.js' ) , 45 | require( './diff.js' ) , 46 | require( './mask.js' ) , 47 | require( './arrayLike.js' ) 48 | ) ; 49 | 50 | -------------------------------------------------------------------------------- /lib/wildDotPath.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | const wildDotPath = {} ; 32 | module.exports = wildDotPath ; 33 | 34 | 35 | 36 | const EMPTY_PATH = [] ; 37 | const PROTO_POLLUTION_MESSAGE = 'This would cause prototype pollution' ; 38 | 39 | 40 | 41 | function toPathArray( path ) { 42 | if ( Array.isArray( path ) ) { 43 | /* 44 | let i , iMax = path.length ; 45 | for ( i = 0 ; i < iMax ; i ++ ) { 46 | if ( typeof path[ i ] !== 'string' || typeof path[ i ] !== 'number' ) { path[ i ] = '' + path[ i ] ; } 47 | } 48 | //*/ 49 | return path ; 50 | } 51 | 52 | if ( ! path ) { return EMPTY_PATH ; } 53 | if ( typeof path === 'string' ) { 54 | return path[ path.length - 1 ] === '.' ? path.slice( 0 , - 1 ).split( '.' ) : path.split( '.' ) ; 55 | } 56 | 57 | throw new TypeError( '[tree.wildDotPath]: the path argument should be a string or an array' ) ; 58 | } 59 | 60 | // Expose toPathArray() 61 | wildDotPath.toPathArray = toPathArray ; 62 | 63 | 64 | 65 | // Walk the tree and return an array of values. 66 | function wildWalk( object , pathArray , maxOffset = 0 , index = 0 , result = [] ) { 67 | var indexMax , key , 68 | pointer = object ; 69 | 70 | for ( indexMax = pathArray.length + maxOffset ; index < indexMax ; index ++ ) { 71 | key = pathArray[ index ] ; 72 | 73 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 74 | if ( ! pointer || typeof pointer !== 'object' ) { return result ; } 75 | 76 | if ( key === '*' ) { 77 | for ( let subPointer of Array.isArray( pointer ) ? pointer : Object.values( pointer ) ) { 78 | wildWalk( subPointer , pathArray , maxOffset , index + 1 , result ) ; 79 | } 80 | 81 | return result ; 82 | } 83 | 84 | pointer = pointer[ key ] ; 85 | } 86 | 87 | result.push( pointer ) ; 88 | 89 | return result ; 90 | } 91 | 92 | 93 | 94 | const VALUE_LIST_MODE = 0 ; 95 | const PATH_LIST_MODE = 1 ; 96 | const PATH_VALUE_MAP_MODE = 2 ; 97 | 98 | // Same than wildWalk, but either return an array of path, or an object of path => value. 99 | function wildWalkPathValue( object , pathArray , resultMode , maxOffset = 0 , index = 0 , path = '' , result = null ) { 100 | if ( ! result ) { 101 | result = resultMode === PATH_VALUE_MAP_MODE ? {} : [] ; 102 | } 103 | 104 | var indexMax , key , 105 | pointer = object ; 106 | 107 | for ( indexMax = pathArray.length + maxOffset ; index < indexMax ; index ++ ) { 108 | key = pathArray[ index ] ; 109 | 110 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 111 | if ( ! pointer || typeof pointer !== 'object' ) { return result ; } 112 | 113 | if ( key === '*' ) { 114 | if ( Array.isArray( pointer ) ) { 115 | let subKey = 0 ; 116 | for ( let subPointer of pointer ) { 117 | wildWalkPathValue( subPointer , pathArray , resultMode , maxOffset , index + 1 , path ? path + '.' + subKey : '' + subKey , result ) ; 118 | subKey ++ ; 119 | } 120 | } 121 | else { 122 | for ( let [ subKey , subPointer ] of Object.entries( pointer ) ) { 123 | wildWalkPathValue( subPointer , pathArray , resultMode , maxOffset , index + 1 , path ? path + '.' + subKey : '' + subKey , result ) ; 124 | } 125 | } 126 | 127 | return result ; 128 | } 129 | 130 | path = path ? path + '.' + key : key ; 131 | pointer = pointer[ key ] ; 132 | } 133 | 134 | if ( resultMode === VALUE_LIST_MODE ) { 135 | result.push( pointer ) ; 136 | } 137 | else if ( resultMode === PATH_LIST_MODE ) { 138 | result.push( path ) ; 139 | } 140 | else if ( resultMode === PATH_VALUE_MAP_MODE ) { 141 | result[ path ] = pointer ; 142 | } 143 | 144 | return result ; 145 | } 146 | 147 | 148 | 149 | // Get with wildcard, return an array 150 | wildDotPath.get = wildDotPath.getValues = ( object , path ) => wildWalk( object , toPathArray( path ) ) ; 151 | wildDotPath.getPaths = ( object , path ) => wildWalkPathValue( object , toPathArray( path ) , PATH_LIST_MODE ) ; 152 | wildDotPath.getPathValueMap = ( object , path ) => wildWalkPathValue( object , toPathArray( path ) , PATH_VALUE_MAP_MODE ) ; 153 | 154 | 155 | 156 | // Walk and pave the tree and exec a hook at leaf. 157 | // Object MUST be an object! no check are performed for the first step! 158 | function wildWalkPave( object , pathArray , leafHookFn , leafHookArg , index = 0 ) { 159 | var indexMax , key , 160 | pointer = object ; 161 | 162 | for ( indexMax = pathArray.length - 1 ; index < indexMax ; index ++ ) { 163 | key = pathArray[ index ] ; 164 | 165 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } 166 | if ( ! pointer || typeof pointer !== 'object' ) { return 0 ; } 167 | 168 | if ( key === '*' ) { 169 | let count = 0 ; 170 | 171 | for ( let subPointer of Array.isArray( pointer ) ? pointer : Object.values( pointer ) ) { 172 | count += wildWalkPave( subPointer , pathArray , leafHookFn , leafHookArg , index + 1 ) ; 173 | } 174 | 175 | return count ; 176 | } 177 | 178 | // Pave 179 | if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = {} ; } 180 | 181 | pointer = pointer[ key ] ; 182 | } 183 | 184 | // Last key of the path 185 | key = pathArray[ index ] ; 186 | 187 | if ( key === '*' ) { 188 | if ( ! pointer || typeof pointer !== 'object' ) { return 0 ; } 189 | 190 | let count = 0 ; 191 | 192 | if ( Array.isArray( pointer ) ) { 193 | for ( let subKey = 0 ; subKey < pointer.length ; subKey ++ ) { 194 | count += leafHookFn( pointer , subKey , leafHookArg ) ; 195 | } 196 | } 197 | else { 198 | for ( let subKey of Object.keys( pointer ) ) { 199 | count += leafHookFn( pointer , subKey , leafHookArg ) ; 200 | } 201 | } 202 | 203 | return count ; 204 | } 205 | 206 | return leafHookFn( pointer , key , leafHookArg ) ; 207 | } 208 | 209 | 210 | 211 | const SET_HOOK = ( pointer , key , value ) => { 212 | pointer[ key ] = value ; 213 | return 1 ; 214 | } ; 215 | 216 | wildDotPath.set = ( object , path , value ) => { 217 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 218 | return wildWalkPave( object , toPathArray( path ) , SET_HOOK , value ) ; 219 | } ; 220 | 221 | 222 | 223 | const DEFINE_HOOK = ( pointer , key , value ) => { 224 | if ( key in pointer ) { return 0 ; } 225 | pointer[ key ] = value ; 226 | return 1 ; 227 | } ; 228 | 229 | wildDotPath.define = ( object , path , value ) => { 230 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 231 | return wildWalkPave( object , toPathArray( path ) , DEFINE_HOOK , value ) ; 232 | } ; 233 | 234 | 235 | 236 | const INC_HOOK = ( pointer , key ) => { 237 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] ++ ; } 238 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = 1 ; } 239 | return 1 ; 240 | } ; 241 | 242 | wildDotPath.inc = ( object , path ) => { 243 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 244 | return wildWalkPave( object , toPathArray( path ) , INC_HOOK ) ; 245 | } ; 246 | 247 | 248 | 249 | const DEC_HOOK = ( pointer , key ) => { 250 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } 251 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } 252 | return 1 ; 253 | } ; 254 | 255 | wildDotPath.dec = ( object , path ) => { 256 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 257 | return wildWalkPave( object , toPathArray( path ) , DEC_HOOK ) ; 258 | } ; 259 | 260 | 261 | 262 | const CONCAT_HOOK = ( pointer , key , value ) => { 263 | if ( ! pointer[ key ] ) { 264 | pointer[ key ] = value ; 265 | } 266 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) { 267 | pointer[ key ] = pointer[ key ].concat( value ) ; 268 | } 269 | 270 | return 1 ; 271 | } ; 272 | 273 | wildDotPath.concat = ( object , path , value ) => { 274 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 275 | return wildWalkPave( object , toPathArray( path ) , CONCAT_HOOK , value ) ; 276 | } ; 277 | 278 | 279 | 280 | const INSERT_HOOK = ( pointer , key , value ) => { 281 | if ( ! pointer[ key ] ) { 282 | pointer[ key ] = value ; 283 | } 284 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) { 285 | pointer[ key ] = value.concat( pointer[ key ] ) ; 286 | } 287 | 288 | return 1 ; 289 | } ; 290 | 291 | wildDotPath.insert = ( object , path , value ) => { 292 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 293 | return wildWalkPave( object , toPathArray( path ) , INSERT_HOOK , value ) ; 294 | } ; 295 | 296 | 297 | 298 | const DELETE_HOOK = ( pointer , key ) => { 299 | if ( ! Object.hasOwn( pointer , key ) ) { return 0 ; } 300 | delete pointer[ key ] ; 301 | return 1 ; 302 | } ; 303 | 304 | wildDotPath.delete = ( object , path ) => { 305 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 306 | return wildWalkPave( object , toPathArray( path ) , DELETE_HOOK ) ; 307 | } ; 308 | 309 | 310 | 311 | const AUTOPUSH_HOOK = ( pointer , key , value ) => { 312 | if ( pointer[ key ] === undefined ) { pointer[ key ] = value ; } 313 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; } 314 | else { pointer[ key ] = [ pointer[ key ] , value ] ; } 315 | 316 | return 1 ; 317 | } ; 318 | 319 | wildDotPath.autoPush = ( object , path , value ) => { 320 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 321 | return wildWalkPave( object , toPathArray( path ) , AUTOPUSH_HOOK , value ) ; 322 | } ; 323 | 324 | 325 | 326 | const APPEND_HOOK = ( pointer , key , value ) => { 327 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } 328 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; } 329 | return 1 ; 330 | } ; 331 | 332 | wildDotPath.append = ( object , path , value ) => { 333 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 334 | return wildWalkPave( object , toPathArray( path ) , APPEND_HOOK , value ) ; 335 | } ; 336 | 337 | 338 | 339 | const PREPEND_HOOK = ( pointer , key , value ) => { 340 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } 341 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].unshift( value ) ; } 342 | return 1 ; 343 | } ; 344 | 345 | wildDotPath.prepend = ( object , path , value ) => { 346 | if ( ! object || typeof object !== 'object' ) { return 0 ; } 347 | return wildWalkPave( object , toPathArray( path ) , PREPEND_HOOK , value ) ; 348 | } ; 349 | 350 | -------------------------------------------------------------------------------- /log/README: -------------------------------------------------------------------------------- 1 | This force git to create this folder. -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tree-kit", 3 | "version": "0.8.8", 4 | "description": "Tree utilities which provides a full-featured extend and object-cloning facility, and various tools to deal with nested object structures.", 5 | "main": "lib/tree.js", 6 | "directories": { 7 | "test": "test", 8 | "bench": "bench" 9 | }, 10 | "engines": { 11 | "node": ">=16.13.0" 12 | }, 13 | "devDependencies": { 14 | "browserify": "^17.0.0", 15 | "uglify-es": "^3.3.9" 16 | }, 17 | "scripts": { 18 | "test": "tea-time -R dot" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "https://github.com/cronvel/tree-kit.git" 23 | }, 24 | "keywords": [ 25 | "tree", 26 | "extend", 27 | "clone", 28 | "prototype", 29 | "inherit", 30 | "deep", 31 | "diff", 32 | "mask" 33 | ], 34 | "author": "Cédric Ronvel", 35 | "license": "MIT", 36 | "bugs": { 37 | "url": "https://github.com/cronvel/tree-kit/issues" 38 | }, 39 | "config": { 40 | "tea-time": { 41 | "coverDir": [ 42 | "lib" 43 | ] 44 | } 45 | }, 46 | "copyright": { 47 | "title": "Tree Kit", 48 | "years": [ 49 | 2014, 50 | 2021 51 | ], 52 | "owner": "Cédric Ronvel" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /sample/bigDeepObject.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | var i2 = 0 , d = 0 , o = {} ; 27 | module.exports = o ; 28 | 29 | 30 | function create( o ) 31 | { 32 | var i , j , l , k , v ; 33 | 34 | for ( i = 0 ; i < 20 && (++i2) < 1000 ; i ++ ) 35 | { 36 | k = '' ; 37 | 38 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 30 ) ; j < l ; j ++ ) 39 | { 40 | k += String.fromCharCode( 0x61 + Math.floor( Math.random() * 26 ) ) ; 41 | } 42 | 43 | switch ( i % 4 ) 44 | { 45 | case 0 : 46 | v = '' ; 47 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 100 ) ; j < l ; j ++ ) 48 | { 49 | v += String.fromCharCode( 0x20 + Math.floor( Math.random() * 0x60 ) ) ; 50 | } 51 | break ; 52 | case 1 : 53 | v = Math.random() * 1000000 ; 54 | break ; 55 | case 2 : 56 | v = Math.random() > 0.8 ? null : ( Math.random() > 0.5 ? true : false ) ; 57 | break ; 58 | case 3 : 59 | if ( d > 100 ) { break ; } 60 | v = {} ; 61 | d ++ ; 62 | create( v ) ; 63 | d -- ; 64 | break ; 65 | } 66 | 67 | o[ k ] = v ; 68 | } 69 | } 70 | 71 | create( o ) ; 72 | 73 | //console.log( JSON.stringify( o , null , ' ' ) ) ; 74 | -------------------------------------------------------------------------------- /sample/bigFlatObject.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | var i , j , l , k , v , o = {} ; 27 | module.exports = o ; 28 | 29 | 30 | 31 | for ( i = 0 ; i < 10000 ; i ++ ) 32 | { 33 | k = '' ; 34 | 35 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 30 ) ; j < l ; j ++ ) 36 | { 37 | k += String.fromCharCode( 0x61 + Math.floor( Math.random() * 26 ) ) ; 38 | } 39 | 40 | switch ( i % 3 ) 41 | { 42 | case 0 : 43 | v = '' ; 44 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 100 ) ; j < l ; j ++ ) 45 | { 46 | v += String.fromCharCode( 0x20 + Math.floor( Math.random() * 0x60 ) ) ; 47 | } 48 | break ; 49 | case 1 : 50 | v = Math.random() * 1000000 ; 51 | break ; 52 | case 2 : 53 | v = Math.random() > 0.8 ? null : ( Math.random() > 0.5 ? true : false ) ; 54 | break ; 55 | } 56 | 57 | o[ k ] = v ; 58 | } 59 | -------------------------------------------------------------------------------- /sample/garbageStringObject.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | var i , j , l , k , v , o = {} ; 27 | module.exports = o ; 28 | 29 | 30 | 31 | for ( i = 0 ; i < 100 ; i ++ ) 32 | { 33 | k = '' ; 34 | v = '' ; 35 | 36 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 30 ) ; j < l ; j ++ ) 37 | { 38 | k += String.fromCharCode( Math.floor( Math.random() * 256 ) ) ; 39 | } 40 | 41 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 1000 ) ; j < l ; j ++ ) 42 | { 43 | v += String.fromCharCode( Math.floor( Math.random() * 256 ) ) ; 44 | } 45 | 46 | o[ k ] = v ; 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /sample/sample1.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | }, 4 | { 5 | "image": [ 6 | {"shape": "rect", "fill": "#333", "stroke": "#999", "x": 0.5e+1, "y": 0.5, "z": 0.8e-0, "w": 0.5e5, "u": 2E10, "foo": 2E+1, "bar": 2E-0, "width": 47, "height": 47} 7 | ], 8 | "jumpable": 3, 9 | "solid": { 10 | "1": [2,4], 11 | "2": [], 12 | "3": [2,6], 13 | "4": [], 14 | "5": [2,8,1,3,7,9,4,6], 15 | "6": [], 16 | "7": [4,8], 17 | "8": [], 18 | "9": [6,8] 19 | }, 20 | "corners": {"1": true,"3": true,"7": true,"9": true} 21 | }, 22 | { 23 | "image": [ 24 | {"shape": "polygon", "fill": "#248", "stroke": "#48f", "points": [[0.5,47.5],[47.5,47.5],[47.5,0.5]]} 25 | ], 26 | "solid": { 27 | "1": [2,4], 28 | "2": [1], 29 | "3": [2], 30 | "4": [], 31 | "5": [2,8,1,3,7,9,4,6], 32 | "6": [], 33 | "7": [4,8], 34 | "8": [], 35 | "9": [6,8] 36 | }, 37 | "corners": {"1": true,"3": true,"7": false,"9": true} 38 | }, 39 | { 40 | "image": [ 41 | {"shape": "polygon", "fill": "#248", "stroke": "#48f", "points": [[0.5,0.5],[47.5,47.5],[0.5,47.5]]} 42 | ], 43 | "solid": { 44 | "1": [2], 45 | "2": [3], 46 | "3": [2,6], 47 | "4": [], 48 | "5": [2,8,1,3,7,9,4,6], 49 | "6": [], 50 | "7": [4,8], 51 | "8": [], 52 | "9": [6,8] 53 | }, 54 | "corners": {"1": true,"3": true,"7": true,"9": false} 55 | }, 56 | { 57 | "image": [ 58 | {"shape": "polygon", "fill": "#333", "stroke": "#999", "points": [[0.5,0.5],[47.5,47.5],[47.5,0.5]]} 59 | ], 60 | "jumpable": 3, 61 | "solid": { 62 | "1": [2,4], 63 | "2": [], 64 | "3": [2,6], 65 | "4": [], 66 | "5": [2,8,1,3,7,9,4,6], 67 | "6": [3], 68 | "7": [4,8], 69 | "8": [7], 70 | "9": [6,8] 71 | }, 72 | "corners": {"1": false,"3": true,"7": true,"9": true} 73 | }, 74 | { 75 | "image": [ 76 | {"shape": "polygon", "fill": "#333", "stroke": "#999", "points": [[0.5,0.5],[0.5,47.5],[47.5,0.5]]} 77 | ], 78 | "jumpable": 3, 79 | "solid": { 80 | "1": [2,4], 81 | "2": [], 82 | "3": [2,6], 83 | "4": [1], 84 | "5": [2,8,1,3,7,9,4,6], 85 | "6": [], 86 | "7": [4,8], 87 | "8": [9], 88 | "9": [6,8] 89 | }, 90 | "corners": {"1": true,"3": false,"7": true,"9": true} 91 | }, 92 | { 93 | "image": [ 94 | {"shape": "polygon", "fill": "#482", "stroke": "#8f4", "points": [[0.5,47.5],[0.5,23.5],[24.5,23.5],[24.5,0.5],[47.5,0.5],[47.5,47.5]]} 95 | ], 96 | "jumpable": 3, 97 | "solid": { 98 | "1": [2,4], 99 | "2": [], 100 | "3": [6,2], 101 | "4": [], 102 | "5": [2,8,1,3,7,9,4,6], 103 | "6": [9], 104 | "7": [4,8], 105 | "8": [], 106 | "9": [6,8] 107 | }, 108 | "corners": {"1": true,"3": true,"7": false,"9": true} 109 | }, 110 | { 111 | "image": [ 112 | {"shape": "polygon", "fill": "#482", "stroke": "#8f4", "points": [[0.5,0.5],[23.5,0.5],[23.5,24.5],[47.5,24.5],[47.5,47.5],[0.5,47.5]]} 113 | ], 114 | "jumpable": 3, 115 | "solid": { 116 | "1": [4,2], 117 | "2": [], 118 | "3": [2,6], 119 | "4": [7], 120 | "5": [2,8,1,3,7,9,4,6], 121 | "6": [], 122 | "7": [4,8], 123 | "8": [], 124 | "9": [6,8] 125 | }, 126 | "corners": {"1": true,"3": true,"7": true,"9": false} 127 | }, 128 | { 129 | "image": [ 130 | {"shape": "circle", "fill": "#ff0", "stroke": "#ff8", "cx": 24, "cy": 24, "r": 18} 131 | ], 132 | "item": true 133 | }, 134 | { 135 | "image": [ 136 | {"shape": "polygon", "fill": "#842", "stroke": "#f84", "points": [[4.5,0.5],[14.5,0.5],[14.5,17.5],[34,17.5],[33.5,0.5],[43.5,0.5],[43.5,47.5],[33.5,47.5],[33.5,30.5],[14.5,30.5],[14.5,47.5],[4.5,47.5]]} 137 | ], 138 | "jumpable": 3 139 | }, 140 | { 141 | "image": [ 142 | {"shape": "polygon", "fill": "#333", "stroke": "#999", "points": [[0.5,0.5],[47.5,0.5],[24,47.5]]} 143 | ], 144 | "jumpable": 3, 145 | "solid": { 146 | "1": [2,4], 147 | "2": [], 148 | "3": [2,6], 149 | "4": [1], 150 | "5": [2,8,1,3,7,9,4,6], 151 | "6": [3], 152 | "7": [4,8], 153 | "8": [], 154 | "9": [6,8] 155 | }, 156 | "corners": {"1": false,"3": false,"7": true,"9": true} 157 | }, 158 | { 159 | "image": [ 160 | {"shape": "rect", "fill": "#114acb", "x": 0.5, "y": 0.5, "width": 47, "height": 47}, 161 | {"shape": "polygon", "fill": "rgba(255,255,255,0.30)", "points": [[0.5,0.5],[47.5,0.5],[40,8],[8,8],[8,40],[0.5,47.5]]}, 162 | {"shape": "polygon", "fill": "rgba(0,0,0,0.30)", "points": [[47.5,0.5],[48,48],[0.5,47.5],[8,40],[40,40],[40,8]]}, 163 | {"shape": "polygon", "fill": "rgb(255,255,0)", "stroke": "rgba(255,255,0,0.5)", "points": [[24,9],[35,20],[26,29],[26,33],[22,33],[22,27],[29,20],[24,15],[16,23],[13,20]]}, 164 | {"shape": "rect", "fill": "rgb(255,255,0)", "stroke": "rgba(255,255,0,0.5)", "x": 22, "y":35, "width": 4, "height": 4} 165 | ] 166 | } 167 | ] 168 | -------------------------------------------------------------------------------- /sample/stringFlatObject.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | var i , j , l , k , v , o = {} ; 27 | module.exports = o ; 28 | 29 | 30 | 31 | for ( i = 0 ; i < 100 ; i ++ ) 32 | { 33 | k = '' ; 34 | v = '' ; 35 | 36 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 30 ) ; j < l ; j ++ ) 37 | { 38 | k += String.fromCharCode( 0x20 + Math.floor( Math.random() * 0x5f ) ) ; 39 | } 40 | 41 | for ( j = 0 , l = 1 + Math.floor( Math.random() * 1000 ) ; j < l ; j ++ ) 42 | { 43 | v += String.fromCharCode( 0x20 + Math.floor( Math.random() * 0x5f ) ) ; 44 | } 45 | 46 | o[ k ] = v ; 47 | } 48 | 49 | 50 | -------------------------------------------------------------------------------- /test/arrayLike-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /* global expect, describe, it */ 28 | 29 | "use strict" ; 30 | 31 | 32 | 33 | const arrayLike = require( '../lib/arrayLike.js' ) ; 34 | 35 | 36 | 37 | describe( "Array-like methods" , () => { 38 | 39 | it( ".map()" , () => { 40 | var object = { 41 | a: { key1: 'value1' , key2: 'value2' } , 42 | b: { key1: 'alt-value1' , key2: 'value2' } , 43 | c: { key1: 'value1' , key2: 'alt-value2' } 44 | } ; 45 | 46 | expect( arrayLike.map( object , item => item.key1 ) ).to.equal( { 47 | a: 'value1' , 48 | b: 'alt-value1' , 49 | c: 'value1' 50 | } ) ; 51 | 52 | expect( arrayLike.map( object , item => item.key1 === 'value1' ) ).to.equal( { 53 | a: true , 54 | b: false , 55 | c: true 56 | } ) ; 57 | 58 | expect( arrayLike.map( object , item => ( { newKey: item.key1 } ) ) ).to.equal( { 59 | a: { newKey: 'value1' } , 60 | b: { newKey: 'alt-value1' } , 61 | c: { newKey: 'value1' } 62 | } ) ; 63 | } ) ; 64 | 65 | it( ".filter()" , () => { 66 | var object = { 67 | a: { key1: 'value1' , key2: 'value2' } , 68 | b: { key1: 'alt-value1' , key2: 'value2' } , 69 | c: { key1: 'value1' , key2: 'alt-value2' } 70 | } ; 71 | 72 | expect( arrayLike.filter( object , item => item.key1 === 'value1' ) ).to.equal( { 73 | a: { key1: 'value1' , key2: 'value2' } , 74 | c: { key1: 'value1' , key2: 'alt-value2' } 75 | } ) ; 76 | 77 | expect( arrayLike.filter( object , item => item.key2 === 'value2' ) ).to.equal( { 78 | a: { key1: 'value1' , key2: 'value2' } , 79 | b: { key1: 'alt-value1' , key2: 'value2' } 80 | } ) ; 81 | } ) ; 82 | } ) ; 83 | 84 | -------------------------------------------------------------------------------- /test/clone-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | const tree = require( '../lib/tree.js' ) ; 32 | 33 | 34 | 35 | describe( "clone()" , function() { 36 | 37 | it( "basic incomplete test" , function() { 38 | var proto = { 39 | proto1: 'proto1' , 40 | proto2: 'proto2' , 41 | hello: function() { console.log( "Hello!" ) ; } 42 | } ; 43 | 44 | var o = Object.create( proto ) ; 45 | 46 | o.own1 = 'own1' ; 47 | o.own2 = 'own2' ; 48 | o.nested = { a: 1 , b: 2 } ; 49 | 50 | var getter = function() { return 5 ; } ; 51 | var setter = function( value ) {} ; 52 | 53 | Object.defineProperties( o , { 54 | nonEnum1: { value: 'nonEnum1' } , 55 | nonEnum2: { value: 'nonEnum2' , writable: true } , 56 | nonEnum3: { value: 'nonEnum3' , configurable: true } , 57 | nonEnumNested: { value: { c: 3 , d: 4 } } , 58 | getter: { get: getter } , 59 | getterAndSetter: { get: getter , set: setter } 60 | } ) ; 61 | 62 | var i , r ; 63 | 64 | 65 | // Basic tests with and without circular checks 66 | for ( i = 0 ; i <= 1 ; i ++ ) 67 | { 68 | if ( i === 0 ) { r = tree.clone( o ) ;} 69 | else { r = tree.clone( o , true ) ; } 70 | 71 | expect( Object.getOwnPropertyNames( r ) ).to.equal( [ 'own1' , 'own2' , 'nested' , 'nonEnum1' , 'nonEnum2' , 'nonEnum3' , 'nonEnumNested' , 'getter' , 'getterAndSetter' ] ) ; 72 | expect( Object.getOwnPropertyDescriptor( r , 'own1' ) ).to.equal( { value: 'own1' , enumerable: true , writable: true , configurable: true } ) ; 73 | expect( Object.getOwnPropertyDescriptor( r , 'own2' ) ).to.equal( { value: 'own2' , enumerable: true , writable: true , configurable: true } ) ; 74 | expect( r.nested ).not.to.be( o.nested ) ; 75 | expect( r.nested ).to.equal( o.nested ) ; 76 | expect( Object.getOwnPropertyDescriptor( r , 'nested' ) ).to.equal( { value: o.nested , enumerable: true , writable: true , configurable: true } ) ; 77 | expect( Object.getOwnPropertyDescriptor( r , 'nonEnum1' ) ).to.equal( { value: 'nonEnum1' , enumerable: false , writable: false , configurable: false } ) ; 78 | expect( Object.getOwnPropertyDescriptor( r , 'nonEnum2' ) ).to.equal( { value: 'nonEnum2' , enumerable: false , writable: true , configurable: false } ) ; 79 | expect( Object.getOwnPropertyDescriptor( r , 'nonEnum3' ) ).to.equal( { value: 'nonEnum3' , enumerable: false , writable: false , configurable: true } ) ; 80 | expect( r.nonEnumNested ).not.to.be( o.nonEnumNested ) ; 81 | expect( r.nonEnumNested ).to.equal( o.nonEnumNested ) ; 82 | expect( Object.getOwnPropertyDescriptor( r , 'nonEnumNested' ) ).to.equal( { value: o.nonEnumNested , enumerable: false , writable: false , configurable: false } ) ; 83 | expect( Object.getOwnPropertyDescriptor( r , 'getter' ) ).to.equal( { get: getter , set: undefined , enumerable: false , configurable: false } ) ; 84 | expect( Object.getOwnPropertyDescriptor( r , 'getterAndSetter' ) ).to.equal( { get: getter , set: setter , enumerable: false , configurable: false } ) ; 85 | 86 | expect( r.__proto__ ).to.equal( proto ) ; // jshint ignore:line 87 | expect( r.proto1 ).to.be( 'proto1' ) ; 88 | expect( r.proto2 ).to.be( 'proto2' ) ; 89 | expect( typeof r.hello ).to.equal( 'function' ) ; 90 | } 91 | } ) ; 92 | 93 | it( "circular references test" , function() { 94 | var c , o = { 95 | a: 'a', 96 | sub: { 97 | b: 'b' 98 | }, 99 | sub2: { 100 | c: 'c' 101 | } 102 | } ; 103 | 104 | o.loop = o ; 105 | o.sub.loop = o ; 106 | o.subcopy = o.sub ; 107 | o.sub.link = o.sub2 ; 108 | o.sub2.link = o.sub ; 109 | 110 | 111 | c = tree.clone( o , true ) ; 112 | 113 | expect( c.loop ).to.be( c ) ; 114 | expect( c.sub ).to.be( c.subcopy ) ; 115 | expect( c.sub.loop ).to.be( c ) ; 116 | expect( c.subcopy.loop ).to.be( c ) ; 117 | expect( c.sub.link ).to.be( c.sub2 ) ; 118 | expect( c.sub2.link ).to.be( c.sub ) ; 119 | } ) ; 120 | 121 | it( "cloning an array" , function() { 122 | var a , c ; 123 | 124 | a = [ 'one' , 'two' , 'three' ] ; 125 | c = tree.clone( a ) ; 126 | expect( c ).to.equal( a ) ; 127 | expect( Array.isArray( c ) ).to.be.ok() ; 128 | 129 | a = [ 'one' , [ 'two' , 'three' ] ] ; 130 | c = tree.clone( a ) ; 131 | expect( c ).to.equal( a ) ; 132 | expect( Array.isArray( c ) ).to.be.ok() ; 133 | expect( Array.isArray( c[ 1 ] ) ).to.be.ok() ; 134 | } ) ; 135 | 136 | it( "cloning a Date instance" , function() { 137 | var a , c ; 138 | 139 | a = new Date() ; 140 | c = tree.clone( a ) ; 141 | expect( c ).to.equal( a ) ; 142 | expect( c ).not.to.be( a ) ; 143 | 144 | a = { d: new Date() } ; 145 | c = tree.clone( a ) ; 146 | expect( c ).to.equal( a ) ; 147 | expect( c ).not.to.be( a ) ; 148 | expect( c.d ).not.to.be( a.d ) ; 149 | } ) ; 150 | 151 | it( "better test suit for clone()" ) ; 152 | } ) ; 153 | 154 | -------------------------------------------------------------------------------- /test/diff-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | var tree = require( '../lib/tree.js' ) ; 32 | 33 | 34 | 35 | 36 | 37 | /* Tests */ 38 | 39 | 40 | 41 | describe( "Diff" , function() { 42 | 43 | it( "should return an array of differences for two objects without nested object" , function() { 44 | var a = { 45 | a: 'a', 46 | b: 2, 47 | c: 'three' 48 | } ; 49 | 50 | var b = { 51 | b: 2, 52 | c: 3, 53 | d: 'dee' 54 | } ; 55 | 56 | var diff = tree.diff( a , b ) ; 57 | 58 | //console.log( JSON.stringify( diff , null , ' ' ) ) ; 59 | expect( diff ).not.to.be( null ) ; 60 | expect( diff ).to.only.have.own.keys( '.a', '.c', '.d' ) ; 61 | } ) ; 62 | 63 | it( "should return an array of differences for two objects with nested objects" , function() { 64 | var a = { 65 | a: 'a', 66 | b: 2, 67 | c: 'three', 68 | sub: { 69 | e: 5, 70 | f: 'six', 71 | subsub: { 72 | g: 'gee', 73 | h: 'h' 74 | } 75 | }, 76 | suba: { 77 | j: 'djay' 78 | } 79 | } ; 80 | 81 | var b = { 82 | b: 2, 83 | c: 3, 84 | d: 'dee', 85 | sub: { 86 | e: 5, 87 | f: 6, 88 | subsub: { 89 | g: 'gee', 90 | i: 'I' 91 | } 92 | }, 93 | subb: { 94 | k: 'k' 95 | } 96 | } ; 97 | 98 | var diff = tree.diff( a , b ) ; 99 | 100 | //console.log( JSON.stringify( diff , null , ' ' ) ) ; 101 | expect( diff ).not.to.be( null ) ; 102 | expect( diff ).to.only.have.own.keys( '.a', '.c', '.d', '.sub.f', '.sub.subsub.h', '.sub.subsub.i', '.suba', '.subb' ) ; 103 | } ) ; 104 | } ) ; 105 | 106 | 107 | 108 | -------------------------------------------------------------------------------- /test/dotPath-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /* global expect, describe, it */ 28 | 29 | "use strict" ; 30 | 31 | 32 | 33 | const dotPath = require( '../lib/dotPath.js' ) ; 34 | 35 | 36 | 37 | describe( "Tree's dot-path on objects" , () => { 38 | 39 | it( "dotPath.get() on object structure" , () => { 40 | var o = { 41 | a: 5 , 42 | sub: { 43 | b: "toto" , 44 | sub: { 45 | c: true 46 | } 47 | } , 48 | d: null 49 | } ; 50 | 51 | expect( dotPath.get( o , 'a' ) ).to.be( 5 ) ; 52 | expect( dotPath.get( o , 'sub' ) ).to.equal( { 53 | b: "toto" , 54 | sub: { 55 | c: true 56 | } 57 | } ) ; 58 | 59 | expect( dotPath.get( o , 'sub.b' ) ).to.be( "toto" ) ; 60 | expect( dotPath.get( o , 'sub.sub' ) ).to.equal( { c: true } ) ; 61 | expect( dotPath.get( o , 'sub.sub.c' ) ).to.be( true ) ; 62 | expect( dotPath.get( o , 'd' ) ).to.be( null ) ; 63 | expect( dotPath.get( o , 'nothing' ) ).to.be( undefined ) ; 64 | expect( dotPath.get( o , 'sub.nothing' ) ).to.be( undefined ) ; 65 | expect( dotPath.get( o , 'nothing.nothing' ) ).to.be( undefined ) ; 66 | } ) ; 67 | 68 | it( "should support the empty path part syntax", () => { 69 | var o = { 70 | "": { 71 | hidden: "value" , 72 | "": { 73 | hidden2: "value2" , 74 | } , 75 | sub: { 76 | "": { 77 | hidden3: "value3" , 78 | } , 79 | } 80 | } 81 | } ; 82 | 83 | expect( dotPath.get( o , '' ) ).to.be( o ) ; 84 | expect( dotPath.get( o , '.' ) ).to.be( o[''] ) ; 85 | expect( dotPath.get( o , '.hidden' ) ).to.be( "value" ) ; 86 | expect( dotPath.get( o , '..' ) ).to.be( o[''][''] ) ; 87 | expect( dotPath.get( o , '..hidden2' ) ).to.be( "value2" ) ; 88 | expect( dotPath.get( o , '.sub' ) ).to.be( o[''].sub ) ; 89 | expect( dotPath.get( o , '.sub..' ) ).to.be( o[''].sub[''] ) ; 90 | expect( dotPath.get( o , '.sub..hidden3' ) ).to.be( "value3" ) ; 91 | } ) ; 92 | 93 | it( "dotPath.delete() on object structure" , () => { 94 | var o = { 95 | a: 5 , 96 | sub: { 97 | b: "toto" , 98 | sub: { 99 | c: true , 100 | sub: { 101 | f: '' 102 | } 103 | } 104 | } , 105 | d: null 106 | } ; 107 | 108 | dotPath.delete( o , 'a' ) ; 109 | dotPath.delete( o , 'sub.sub' ) ; 110 | dotPath.delete( o , 'non.existant.path' ) ; 111 | 112 | expect( o ).to.equal( { 113 | sub: { 114 | b: "toto" 115 | } , 116 | d: null 117 | } ) ; 118 | } ) ; 119 | 120 | it( "dotPath.set() on object structure" , () => { 121 | var o = { 122 | a: 5 , 123 | sub: { 124 | b: "toto" , 125 | sub: { 126 | c: true 127 | } 128 | } , 129 | d: null 130 | } ; 131 | 132 | dotPath.set( o , 'a' , "8" ) ; 133 | dotPath.set( o , 'sub.b' , false ) ; 134 | dotPath.set( o , 'sub.sub' , { x: 18 , y: 27 } ) ; 135 | dotPath.set( o , 'non.existant.path' , 'new' ) ; 136 | 137 | expect( o ).to.equal( { 138 | a: "8" , 139 | sub: { 140 | b: false , 141 | sub: { 142 | x: 18 , 143 | y: 27 144 | } 145 | } , 146 | d: null , 147 | non: { 148 | existant: { 149 | path: 'new' 150 | } 151 | } 152 | } ) ; 153 | } ) ; 154 | 155 | it( "dotPath.define() on object structure" , () => { 156 | var o = { 157 | a: 5 , 158 | sub: { 159 | b: "toto" , 160 | sub: { 161 | c: true 162 | } 163 | } , 164 | d: null 165 | } ; 166 | 167 | dotPath.define( o , 'a' , "8" ) ; 168 | dotPath.define( o , 'sub.b' , false ) ; 169 | dotPath.define( o , 'unexistant' , '!' ) ; 170 | dotPath.define( o , 'sub.sub' , { x: 18 , y: 27 } ) ; 171 | dotPath.define( o , 'non.existant.path' , 'new' ) ; 172 | 173 | expect( o ).to.equal( { 174 | a: 5 , 175 | unexistant: '!' , 176 | sub: { 177 | b: "toto" , 178 | sub: { 179 | c: true 180 | } 181 | } , 182 | d: null , 183 | non: { 184 | existant: { 185 | path: 'new' 186 | } 187 | } 188 | } ) ; 189 | } ) ; 190 | 191 | it( "dotPath.inc() and dotPath.dec() on object structure" , () => { 192 | var o = { 193 | a: 5 , 194 | sub: { 195 | b: 10 , 196 | sub: { 197 | c: true 198 | } 199 | } , 200 | d: null 201 | } ; 202 | 203 | dotPath.inc( o , 'a' ) ; 204 | dotPath.dec( o , 'sub.b' ) ; 205 | dotPath.inc( o , 'sub' ) ; 206 | dotPath.dec( o , 'sub.sub' ) ; 207 | dotPath.inc( o , 'non.existant.path' ) ; 208 | dotPath.dec( o , 'another.non.existant.path' ) ; 209 | 210 | expect( o ).to.equal( { 211 | a: 6 , 212 | sub: { 213 | b: 9 , 214 | sub: { 215 | c: true 216 | } 217 | } , 218 | d: null , 219 | non: { 220 | existant: { 221 | path: 1 222 | } 223 | } , 224 | another: { 225 | non: { 226 | existant: { 227 | path: -1 228 | } 229 | } 230 | } 231 | } ) ; 232 | } ) ; 233 | 234 | it( "dotPath.append() and dotPath.prepend() on object structure" , () => { 235 | var o = { 236 | a: null , 237 | sub: { 238 | b: [ 'some' ] , 239 | sub: { 240 | c: [ 'value' ] 241 | } 242 | } 243 | } ; 244 | 245 | dotPath.append( o , 'a' , 'hello' ) ; 246 | dotPath.append( o , 'sub.b' , 'value' ) ; 247 | dotPath.prepend( o , 'sub.sub.c' , 'other' ) ; 248 | dotPath.prepend( o , 'sub.sub.c' , 'some' ) ; 249 | dotPath.append( o , 'sub.sub.c' , '!' ) ; 250 | dotPath.append( o , 'non.existant.path' , '!' ) ; 251 | dotPath.prepend( o , 'another.non.existant.path' , '!' ) ; 252 | 253 | expect( o ).to.equal( { 254 | a: [ 'hello' ] , 255 | sub: { 256 | b: [ 'some' , 'value' ] , 257 | sub: { 258 | c: [ 'some' , 'other' , 'value' , '!' ] 259 | } 260 | } , 261 | non: { 262 | existant: { 263 | path: [ '!' ] 264 | } 265 | } , 266 | another: { 267 | non: { 268 | existant: { 269 | path: [ '!' ] 270 | } 271 | } 272 | } 273 | } ) ; 274 | } ) ; 275 | 276 | it( "dotPath.concat() and dotPath.insert() on object structure" , () => { 277 | var o = { 278 | a: null , 279 | sub: { 280 | b: [ 'hi' ] , 281 | sub: { 282 | c: [ 'again' ] 283 | } 284 | } 285 | } ; 286 | 287 | dotPath.concat( o , 'a' , [ 'hello' , 'world' ] ) ; 288 | dotPath.concat( o , 'sub.b' , [ 'hello' , 'world' ] ) ; 289 | dotPath.insert( o , 'sub.sub.c' , [ 'hello' , 'world' ] ) ; 290 | 291 | expect( o ).to.equal( { 292 | a: [ 'hello' , 'world' ] , 293 | sub: { 294 | b: [ 'hi' , 'hello' , 'world' ] , 295 | sub: { 296 | c: [ 'hello' , 'world' , 'again' ] 297 | } 298 | } 299 | } ) ; 300 | } ) ; 301 | 302 | it( "dotPath.autoPush()" , () => { 303 | var o = { 304 | a: 1 , 305 | sub: { 306 | b: [ 'some' ] , 307 | sub: { 308 | c: [ 'some' , 'other' , 'value' ] 309 | } 310 | } 311 | } ; 312 | 313 | dotPath.autoPush( o , 'a' , 'hello' ) ; 314 | dotPath.autoPush( o , 'd' , 'D' ) ; 315 | dotPath.autoPush( o , 'sub.b' , 'value' ) ; 316 | dotPath.autoPush( o , 'sub.sub.c' , '!' ) ; 317 | dotPath.autoPush( o , 'non.existant.path' , '!' ) ; 318 | 319 | expect( o ).to.equal( { 320 | a: [ 1 , 'hello' ] , 321 | d: 'D' , 322 | sub: { 323 | b: [ 'some' , 'value' ] , 324 | sub: { 325 | c: [ 'some' , 'other' , 'value' , '!' ] 326 | } 327 | } , 328 | non: { 329 | existant: { 330 | path: '!' 331 | } 332 | } 333 | } ) ; 334 | } ) ; 335 | } ) ; 336 | 337 | 338 | 339 | describe( "Tree's dot-path on arrays" , () => { 340 | 341 | it( "dotPath.get() on a simple array" , () => { 342 | var a = [ 'a' , 'b' , 'c' ] ; 343 | 344 | expect( dotPath.get( a , '0' ) ).to.be( 'a' ) ; 345 | expect( dotPath.get( a , '1' ) ).to.be( 'b' ) ; 346 | expect( dotPath.get( a , '2' ) ).to.be( 'c' ) ; 347 | expect( dotPath.get( a , '3' ) ).to.be( undefined ) ; 348 | expect( dotPath.get( a , 'length' ) ).to.be( 3 ) ; 349 | } ) ; 350 | 351 | it( "dotPath.get() on nested arrays" , () => { 352 | var a = [ 'a' , [ [ 'b' , 'c' ] , 'd' , [ 'e' , 'f' ] ] ] ; 353 | 354 | expect( dotPath.get( a , '0' ) ).to.be( 'a' ) ; 355 | expect( dotPath.get( a , '1' ) ).to.equal( [ [ 'b' , 'c' ] , 'd' , [ 'e' , 'f' ] ] ) ; 356 | expect( dotPath.get( a , '2' ) ).to.be( undefined ) ; 357 | 358 | expect( dotPath.get( a , '1.0' ) ).to.equal( [ 'b' , 'c' ] ) ; 359 | expect( dotPath.get( a , '1.1' ) ).to.equal( 'd' ) ; 360 | expect( dotPath.get( a , '1.2' ) ).to.equal( [ 'e' , 'f' ] ) ; 361 | expect( dotPath.get( a , '1.3' ) ).to.be( undefined ) ; 362 | expect( dotPath.get( a , '1.length' ) ).to.be( 3 ) ; 363 | } ) ; 364 | 365 | it( "dotPath.set() on a simple array" , () => { 366 | var a ; 367 | 368 | a = [ 'a' , 'b' , 'c' ] ; 369 | 370 | dotPath.set( a , '1' , 'B' ) ; 371 | 372 | expect( a ).to.equal( [ 'a' , 'B' , 'c' ] ) ; 373 | expect( dotPath.get( a , 'length' ) ).to.be( 3 ) ; 374 | 375 | a = [ 'a' , 'b' , 'c' ] ; 376 | 377 | dotPath.set( a , '1' , 'BBB' ) ; 378 | 379 | expect( a ).to.equal( [ 'a' , 'BBB' , 'c' ] ) ; 380 | expect( dotPath.get( a , 'length' ) ).to.be( 3 ) ; 381 | } ) ; 382 | 383 | it( "dotPath.delete() on a simple array" , () => { 384 | var a ; 385 | 386 | a = [ 'a' , 'b' , 'c' ] ; 387 | dotPath.delete( a , '1' ) ; 388 | expect( a ).to.equal( [ 'a' , undefined , 'c' ] ) ; 389 | expect( dotPath.get( a , 'length' ) ).to.be( 3 ) ; 390 | 391 | a = [ 'a' , 'b' , 'c' ] ; 392 | dotPath.delete( a , '2' ) ; 393 | expect( a ).to.equal( [ 'a' , 'b' , undefined ] ) ; 394 | expect( dotPath.get( a , 'length' ) ).to.be( 3 ) ; 395 | } ) ; 396 | 397 | it( "dotPath.set() on structure mixing arrays and objects" ) ; 398 | } ) ; 399 | 400 | 401 | 402 | describe( "Tree's dot-path on mixed object and arrays" , () => { 403 | 404 | it( "dotPath.get() on a simple array" , () => { 405 | var a = { 406 | method: 'get' , 407 | populate: [ 'parents' , 'godfather' ] 408 | } ; 409 | 410 | expect( dotPath.get( a , 'method' ) ).to.be( 'get' ) ; 411 | expect( dotPath.get( a , 'populate' ) ).to.equal( [ 'parents' , 'godfather' ] ) ; 412 | expect( dotPath.get( a , 'populate.0' ) ).to.be( 'parents' ) ; 413 | expect( dotPath.get( a , 'populate.1' ) ).to.be( 'godfather' ) ; 414 | expect( dotPath.get( a , 'populate.2' ) ).to.be( undefined ) ; 415 | } ) ; 416 | 417 | it( "dotPath.set() on a simple array" , () => { 418 | var a = { 419 | method: 'get' , 420 | populate: [ 'parent' , 'godfather' ] 421 | } ; 422 | 423 | dotPath.set( a , 'method' , 'post' ) ; 424 | dotPath.set( a , 'populate.0' , 'friends' ) ; 425 | 426 | expect( a ).to.equal( { 427 | method: 'post' , 428 | populate: [ 'friends' , 'godfather' ] 429 | } ) ; 430 | } ) ; 431 | } ) ; 432 | 433 | 434 | 435 | describe( "Tree's array dot-path on objects" , () => { 436 | 437 | it( "dotPath.get() on object structure" , () => { 438 | var o = { 439 | a: 5 , 440 | sub: { 441 | b: "toto" , 442 | sub: { 443 | c: true 444 | } 445 | } , 446 | d: null 447 | } ; 448 | 449 | expect( dotPath.get( o , [ 'a' ] ) ).to.be( 5 ) ; 450 | expect( dotPath.get( o , [ 'sub' ] ) ).to.be.like( { 451 | b: "toto" , 452 | sub: { 453 | c: true 454 | } 455 | } ) ; 456 | 457 | expect( dotPath.get( o , [ 'sub' , 'b' ] ) ).to.be( "toto" ) ; 458 | expect( dotPath.get( o , [ 'sub' , 'sub' ] ) ).to.be.like( { c: true } ) ; 459 | expect( dotPath.get( o , [ 'sub' , 'sub' , 'c' ] ) ).to.be( true ) ; 460 | expect( dotPath.get( o , [ 'd' ] ) ).to.be( null ) ; 461 | expect( dotPath.get( o , [ 'nothing' ] ) ).to.be( undefined ) ; 462 | expect( dotPath.get( o , [ 'sub' , 'nothing' ] ) ).to.be( undefined ) ; 463 | expect( dotPath.get( o , [ 'nothing' , 'nothing' ] ) ).to.be( undefined ) ; 464 | } ) ; 465 | 466 | it( "dotPath.delete() on object structure" , () => { 467 | var o = { 468 | a: 5 , 469 | sub: { 470 | b: "toto" , 471 | sub: { 472 | c: true , 473 | sub: { 474 | f: '' 475 | } 476 | } 477 | } , 478 | d: null 479 | } ; 480 | 481 | dotPath.delete( o , [ 'a' ] ) ; 482 | dotPath.delete( o , [ 'sub' , 'sub' ] ) ; 483 | dotPath.delete( o , [ 'non' , 'existant' , 'path' ] ) ; 484 | 485 | expect( o ).to.be.like( { 486 | sub: { 487 | b: "toto" 488 | } , 489 | d: null 490 | } ) ; 491 | } ) ; 492 | 493 | it( "dotPath.set() on object structure" , () => { 494 | var o = { 495 | a: 5 , 496 | sub: { 497 | b: "toto" , 498 | sub: { 499 | c: true 500 | } 501 | } , 502 | d: null 503 | } ; 504 | 505 | dotPath.set( o , [ 'a' ] , "8" ) ; 506 | dotPath.set( o , [ 'sub' , 'b' ] , false ) ; 507 | dotPath.set( o , [ 'sub' , 'sub' ] , { x: 18 , y: 27 } ) ; 508 | dotPath.set( o , [ 'non' , 'existant' , 'path' ] , 'new' ) ; 509 | 510 | expect( o ).to.be.like( { 511 | a: "8" , 512 | sub: { 513 | b: false , 514 | sub: { 515 | x: 18 , 516 | y: 27 517 | } 518 | } , 519 | d: null , 520 | non: { 521 | existant: { 522 | path: 'new' 523 | } 524 | } 525 | } ) ; 526 | } ) ; 527 | 528 | it( "dotPath.define() on object structure" , () => { 529 | var o = { 530 | a: 5 , 531 | sub: { 532 | b: "toto" , 533 | sub: { 534 | c: true 535 | } 536 | } , 537 | d: null 538 | } ; 539 | 540 | dotPath.define( o , [ 'a' ] , "8" ) ; 541 | dotPath.define( o , [ 'sub' , 'b' ] , false ) ; 542 | dotPath.define( o , [ 'unexistant' ] , '!' ) ; 543 | dotPath.define( o , [ 'sub' , 'sub' ] , { x: 18 , y: 27 } ) ; 544 | dotPath.define( o , [ 'non' , 'existant' , 'path' ] , 'new' ) ; 545 | 546 | expect( o ).to.be.like( { 547 | a: 5 , 548 | unexistant: '!' , 549 | sub: { 550 | b: "toto" , 551 | sub: { 552 | c: true 553 | } 554 | } , 555 | d: null , 556 | non: { 557 | existant: { 558 | path: 'new' 559 | } 560 | } 561 | } ) ; 562 | } ) ; 563 | 564 | it( "dotPath.inc() and dotPath.dec() on object structure" , () => { 565 | var o = { 566 | a: 5 , 567 | sub: { 568 | b: 10 , 569 | sub: { 570 | c: true 571 | } 572 | } , 573 | d: null 574 | } ; 575 | 576 | dotPath.inc( o , [ 'a' ] ) ; 577 | dotPath.dec( o , [ 'sub' , 'b' ] ) ; 578 | dotPath.inc( o , [ 'sub' ] ) ; 579 | dotPath.dec( o , [ 'sub' , 'sub' ] ) ; 580 | dotPath.inc( o , [ 'non' , 'existant' , 'path' ] ) ; 581 | dotPath.dec( o , [ 'another' , 'non' , 'existant' , 'path' ] ) ; 582 | 583 | expect( o ).to.be.like( { 584 | a: 6 , 585 | sub: { 586 | b: 9 , 587 | sub: { 588 | c: true 589 | } 590 | } , 591 | d: null , 592 | non: { 593 | existant: { 594 | path: 1 595 | } 596 | } , 597 | another: { 598 | non: { 599 | existant: { 600 | path: -1 601 | } 602 | } 603 | } 604 | } ) ; 605 | 606 | } ) ; 607 | } ) ; 608 | 609 | 610 | 611 | describe( ".dotPath() security issues" , () => { 612 | 613 | it( "Prototype pollution using .__proto__" , () => { 614 | delete Object.prototype.hack ; 615 | expect( () => dotPath.set( {} , '__proto__.hack' , 'hacked' ) ).to.throw() ; 616 | expect( Object.prototype.hack ).to.be.undefined() ; 617 | expect( () => dotPath.set( {} , '__proto__' , 'hacked' ) ).to.throw() ; 618 | expect( () => dotPath.set( {a:{}} , 'a.__proto__' , 'hacked' ) ).to.throw() ; 619 | expect( () => dotPath.delete( {} , '__proto__' , 'hacked' ) ).to.throw() ; 620 | expect( () => dotPath.delete( {} , '__proto__.hack' , 'hacked' ) ).to.throw() ; 621 | } ) ; 622 | 623 | it( "Prototype pollution using a path array: [['__proto__']]" , () => { 624 | delete Object.prototype.hack ; 625 | expect( () => dotPath.set( {} , [['__proto__'],'hack'] , 'hacked' ) ).to.throw() ; 626 | expect( Object.prototype.hack ).to.be.undefined() ; 627 | expect( () => dotPath.set( {} , '__proto__' , 'hacked' ) ).to.throw() ; 628 | expect( () => dotPath.set( {a:{}} , 'a.__proto__' , 'hacked' ) ).to.throw() ; 629 | expect( () => dotPath.delete( {} , '__proto__' , 'hacked' ) ).to.throw() ; 630 | expect( () => dotPath.delete( {} , '__proto__.hack' , 'hacked' ) ).to.throw() ; 631 | } ) ; 632 | 633 | it( "Prototype pollution using .constructor" , () => { 634 | delete Object.prototype.hack ; 635 | expect( () => dotPath.set( {} , 'constructor.prototype' , 'hacked' ) ).to.throw() ; 636 | expect( () => dotPath.set( {} , 'constructor.prototype.hack' , 'hacked' ) ).to.throw() ; 637 | expect( Object.prototype.hack ).to.be.undefined() ; 638 | 639 | // Check that we can still return a function, but not go inside of it 640 | function fn() {} 641 | fn.a = 'A' ; 642 | expect( dotPath.get( { fn } , 'fn' ) ).to.be( fn ) ; 643 | expect( () => dotPath.get( { fn } , 'fn.a' ) ).to.throw() ; 644 | expect( () => dotPath.get( { fn } , 'fn.prototype' ) ).to.throw() ; 645 | expect( () => dotPath.set( { fn } , 'fn.a' , 'B' ) ).to.throw() ; 646 | expect( fn.a ).to.be( 'A' ) ; 647 | 648 | var o = { fn } ; 649 | dotPath.set( o , 'fn' , 'notfn' ) ; 650 | expect( o.fn ).to.be( 'notfn' ) ; 651 | } ) ; 652 | } ) ; 653 | 654 | -------------------------------------------------------------------------------- /test/lazy-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | var tree = require( '../lib/tree.js' ) ; 32 | 33 | 34 | 35 | /* Tests */ 36 | 37 | 38 | 39 | describe( "defineLazyProperty()" , function() { 40 | 41 | it( 'should define property using a getter that after its first execution is reconfigured as its return-value and is not writable' , function() { 42 | 43 | var object = {} ; 44 | var counter = 0 ; 45 | 46 | tree.defineLazyProperty( object , 'myprop' , function() { 47 | counter ++ ; 48 | return counter ; 49 | } ) ; 50 | 51 | expect( object.myprop ).to.be( 1 ) ; 52 | expect( object.myprop ).to.be( 1 ) ; 53 | expect( object.myprop ).to.be( 1 ) ; 54 | expect( counter ).to.be( 1 ) ; 55 | expect( function() { object.myprop ++ ; } ).to.throw() ; 56 | expect( object.myprop ).to.be( 1 ) ; 57 | } ) ; 58 | } ) ; 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /test/mask-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | "use strict" ; 28 | 29 | 30 | 31 | var tree = require( '../lib/tree.js' ) ; 32 | 33 | 34 | 35 | 36 | 37 | /* Helper functions */ 38 | 39 | 40 | 41 | // The input tree used in most tests 42 | 43 | function Func() { console.log( 'Hellow!' ) ; } 44 | Func.prototype.fn1 = function fn1() { console.log( 'fn1' ) ; } ; 45 | Func.prototype.fn2 = function fn2() { console.log( 'fn2' ) ; } ; 46 | Func.prop = 'property' ; 47 | 48 | 49 | 50 | var input = { 51 | 'undefined' : undefined , 52 | 'null' : null , 53 | 'bool' : true , 54 | 'true' : true , 55 | 'false' : false , 56 | int : 12 , 57 | float : 2.47 , 58 | string : 'Oh my god' , 59 | scalar : "42" , 60 | email : 'toto@example.com' , 61 | array : [ 1 , 2 , 3 ] , 62 | object : {} , 63 | range : 47 , 64 | color : 'blue' , 65 | note : 'sol' , 66 | size : 37 , 67 | useless : 'useless' , 68 | attachement : { 69 | filename : 'preview.png' , 70 | md5 : 'a20f6c76483c9702ee8f29ed42972417' 71 | } , 72 | files: { 73 | 'background.png' : { 74 | size : '97856' , 75 | md5 : 'ddc349bd71d7bc5411471cb427a8a2f5' 76 | } , 77 | 'header.png' : { 78 | size : '44193' , 79 | md5 : '6bf8104c8bcdb502fdba01dde5bada6a' 80 | } , 81 | 'footer.png' : { 82 | size : '36411' , 83 | md5 : '69f82995d2fb6ae540fff4caa725b6b7' 84 | } 85 | } , 86 | filesArray: [ 87 | { 88 | name : 'background.png' , 89 | size : '97856' , 90 | md5 : 'ddc349bd71d7bc5411471cb427a8a2f5' 91 | } , 92 | { 93 | name : 'header.png' , 94 | size : '44193' , 95 | md5 : '6bf8104c8bcdb502fdba01dde5bada6a' 96 | } , 97 | { 98 | name : 'footer.png' , 99 | size : '36411' , 100 | md5 : '69f82995d2fb6ae540fff4caa725b6b7' 101 | } 102 | ] , 103 | subtree : { 104 | a : 'A' , 105 | b : 2 , 106 | subtree: { 107 | d : 4 , 108 | e : undefined , 109 | f : 3.14 110 | } , 111 | c : 'plusplus' , 112 | subtree2: { 113 | g : 6 , 114 | h : [] , 115 | i : 'iii' 116 | } 117 | } , 118 | anotherSubtree : { 119 | j : 'Djay' , 120 | k : 'ok' , 121 | subtree: { 122 | l : '1one' , 123 | m : false , 124 | n : 'nay' 125 | } , 126 | o : 'mg' , 127 | subtree2: { 128 | p : true , 129 | q : [4,5,6] , 130 | r : '2' 131 | } 132 | } , 133 | subtreeWithFunction: { 134 | z: 'Zee', 135 | Func: Func 136 | } 137 | } ; 138 | 139 | 140 | 141 | /* Tests */ 142 | 143 | 144 | 145 | describe( "Masks" , function() { 146 | 147 | it( 'should apply a simple mask tree to the input tree' , function() { 148 | 149 | var mask = tree.createMask( { 150 | int: true, 151 | float: true, 152 | attachement: { 153 | filename: true, 154 | unexistant: true 155 | }, 156 | unexistant: true, 157 | subtree: { 158 | subtree: true 159 | } 160 | } ) ; 161 | 162 | var output = mask.applyTo( input ) ; 163 | 164 | expect( output ).to.eql( { 165 | int: 12, 166 | float: 2.47, 167 | attachement: { 168 | filename: 'preview.png' 169 | }, 170 | subtree: { 171 | subtree: { 172 | d: 4, 173 | e: undefined, 174 | f: 3.14 175 | } 176 | } 177 | } ) ; 178 | } ) ; 179 | 180 | it( "should apply a mask tree with wildcard '*' to the input tree" , function() { 181 | 182 | var mask = tree.createMask( { 183 | 'files': { 184 | '*': { 185 | size: true, 186 | unexistant: true 187 | } 188 | } 189 | } ) ; 190 | 191 | var output = mask.applyTo( input ) ; 192 | 193 | expect( output.files ).to.be.an( Object ) ; 194 | expect( output ).to.eql( { 195 | files: { 196 | 'background.png' : { 197 | size : '97856' 198 | } , 199 | 'header.png' : { 200 | size : '44193' 201 | } , 202 | 'footer.png' : { 203 | size : '36411' 204 | } 205 | } 206 | } ) ; 207 | } ) ; 208 | 209 | it( "should apply a mask tree with wildcard '*' to match array in the input tree" , function() { 210 | 211 | var mask = tree.createMask( { 212 | 'filesArray': { 213 | '*': { 214 | name: true, 215 | size: true, 216 | unexistant: true 217 | } 218 | } 219 | } ) ; 220 | 221 | var output = mask.applyTo( input ) ; 222 | 223 | expect( output.filesArray ).to.be.an( Array ) ; 224 | expect( output ).to.eql( { 225 | filesArray: [ 226 | { 227 | name : 'background.png' , 228 | size : '97856' 229 | } , 230 | { 231 | name : 'header.png' , 232 | size : '44193' 233 | } , 234 | { 235 | name : 'footer.png' , 236 | size : '36411' 237 | } 238 | ] 239 | } ) ; 240 | 241 | //console.log( "\n\n\n\n" , output , "\n\n\n\n" ) ; 242 | 243 | } ) ; 244 | 245 | it( "should apply a mask with a mask's leaf callback to the input tree" , function() { 246 | 247 | var leaf = function leaf( input , key , argument , path ) { 248 | //console.log( 'LEAF: ' , input , key , argument , path ) ; 249 | 250 | if ( ! input.hasOwnProperty( key ) ) { return new Error( 'not_found' ) ; } 251 | if ( typeof input[ key ] === 'number' ) { return input[ key ] + argument ; } 252 | return input[ key ] ; 253 | } ; 254 | 255 | var mask = tree.createMask( 256 | { 257 | int: 87 , 258 | float: 14 , 259 | subtree: { 260 | subtree: { 261 | f: 0.0016 262 | } 263 | } , 264 | unexistant: 45 265 | } , 266 | { leaf: leaf } 267 | ) ; 268 | 269 | var output = mask.applyTo( input ) ; 270 | 271 | expect( output ).to.eql( { 272 | int: 99, 273 | float: 16.47, 274 | subtree: { 275 | subtree: { 276 | f: 3.1416 277 | } 278 | } 279 | } ) ; 280 | } ) ; 281 | 282 | it( 'should apply a mask containing other masks to the input tree' , function() { 283 | 284 | var mask = tree.createMask( { 285 | int: true, 286 | float: true, 287 | attachement: tree.createMask( { 288 | filename: true, 289 | unexistant: true 290 | } ), 291 | unexistant: true, 292 | subtree: tree.createMask( { 293 | subtree: true 294 | } ) 295 | } ) ; 296 | 297 | var output = mask.applyTo( input ) ; 298 | 299 | expect( output ).to.eql( { 300 | int: 12, 301 | float: 2.47, 302 | attachement: { 303 | filename: 'preview.png' 304 | }, 305 | subtree: { 306 | subtree: { 307 | d: 4, 308 | e: undefined, 309 | f: 3.14 310 | } 311 | } 312 | } ) ; 313 | } ) ; 314 | 315 | it( 'variantes' ) ; 316 | /* 317 | // Variante 1 318 | var mask1 = { 319 | a: true, 320 | b: true, 321 | c: true, 322 | sub: { 323 | subA: true, 324 | subB: true, 325 | subC: true 326 | } 327 | } ; 328 | 329 | // Variante 2 330 | var mask2 = [ 331 | 'a', 'b', 'c', [ 'sub' , [ 'subA' , 'subB' , 'subC' ] ] 332 | ] ; 333 | 334 | // Variante 3 335 | var mask3 = [ 336 | 'a', 'b', 'c', [ 'sub' , 'subA' , 'subB' , 'subC' ] 337 | ] ; 338 | */ 339 | } ) ; 340 | 341 | 342 | 343 | describe( "Inverse masks" , function() { 344 | 345 | it( 'should apply a simple mask tree to the input tree' , function() { 346 | 347 | var mask = tree.createInverseMask( { 348 | a: true, 349 | subtree: { 350 | d: true 351 | }, 352 | subtree2: true 353 | } ) ; 354 | 355 | var output = mask.applyTo( input.subtree ) ; 356 | 357 | //console.log( output ) ; 358 | 359 | expect( output ).to.eql( { 360 | b: 2, 361 | subtree: { 362 | e: undefined, 363 | f: 3.14 364 | }, 365 | c: 'plusplus' 366 | } ) ; 367 | } ) ; 368 | } ) ; 369 | 370 | 371 | -------------------------------------------------------------------------------- /test/wildDotPath-test.js: -------------------------------------------------------------------------------- 1 | /* 2 | Tree Kit 3 | 4 | Copyright (c) 2014 - 2021 Cédric Ronvel 5 | 6 | The MIT License (MIT) 7 | 8 | Permission is hereby granted, free of charge, to any person obtaining a copy 9 | of this software and associated documentation files (the "Software"), to deal 10 | in the Software without restriction, including without limitation the rights 11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | copies of the Software, and to permit persons to whom the Software is 13 | furnished to do so, subject to the following conditions: 14 | 15 | The above copyright notice and this permission notice shall be included in all 16 | copies or substantial portions of the Software. 17 | 18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | SOFTWARE. 25 | */ 26 | 27 | /* global expect, describe, it */ 28 | 29 | "use strict" ; 30 | 31 | 32 | 33 | const wildDotPath = require( '../lib/wildDotPath.js' ) ; 34 | 35 | 36 | 37 | describe( "Tree's wild-dot-path" , () => { 38 | 39 | it( ".get()/.getPaths()/.getPathValueMap() on array of objects" , () => { 40 | var a = { 41 | embedded: [ 42 | { firstName: 'Bobby' , lastName: 'Fischer' } , 43 | { firstName: 'Bob' , lastName: 'Ross' } , 44 | { firstName: 'Joe' , lastName: 'Doe' } 45 | ] 46 | } ; 47 | 48 | expect( wildDotPath.get( a , 'embedded.*' ) ).to.equal( [ 49 | { firstName: 'Bobby' , lastName: 'Fischer' } , 50 | { firstName: 'Bob' , lastName: 'Ross' } , 51 | { firstName: 'Joe' , lastName: 'Doe' } 52 | ] ) ; 53 | expect( wildDotPath.get( a , 'embedded.0.firstName' ) ).to.equal( [ 'Bobby' ] ) ; 54 | expect( wildDotPath.get( a , 'embedded.1.firstName' ) ).to.equal( [ 'Bob' ] ) ; 55 | expect( wildDotPath.get( a , 'embedded.3.firstName' ) ).to.equal( [] ) ; 56 | expect( wildDotPath.get( a , 'embedded.*.firstName' ) ).to.equal( [ 'Bobby' , 'Bob' , 'Joe' ] ) ; 57 | expect( wildDotPath.get( a , 'embedded.*.lastName' ) ).to.equal( [ 'Fischer' , 'Ross' , 'Doe' ] ) ; 58 | expect( wildDotPath.get( a , 'embedded.*.unexistant' ) ).to.equal( [ undefined , undefined , undefined ] ) ; 59 | expect( wildDotPath.get( a , 'embedded.1.firstName' ) ).to.equal( [ 'Bob' ] ) ; 60 | expect( wildDotPath.get( a , 'unexistant.*.unexistant' ) ).to.equal( [] ) ; 61 | expect( wildDotPath.get( { a: 1 } , 'a.*' ) ).to.equal( [] ) ; 62 | expect( wildDotPath.get( 1 , 'a.*' ) ).to.equal( [] ) ; 63 | 64 | expect( wildDotPath.getPaths( a , 'embedded.*.firstName' ) ).to.equal( [ 65 | "embedded.0.firstName" , 66 | "embedded.1.firstName" , 67 | "embedded.2.firstName" 68 | ] ) ; 69 | expect( wildDotPath.getPathValueMap( a , 'embedded.*.firstName' ) ).to.equal( { 70 | "embedded.0.firstName": 'Bobby' , 71 | "embedded.1.firstName": 'Bob' , 72 | "embedded.2.firstName": 'Joe' 73 | } ) ; 74 | 75 | a = [ { name: 'Alice' } , { name: 'Bob' } , { name: 'Charlie' } ] ; 76 | expect( wildDotPath.getPaths( a , '*.name' ) ).to.equal( [ 77 | "0.name" , 78 | "1.name" , 79 | "2.name" 80 | ] ) ; 81 | expect( wildDotPath.getPathValueMap( a , '*.name' ) ).to.equal( { 82 | "0.name": 'Alice' , 83 | "1.name": 'Bob' , 84 | "2.name": 'Charlie' 85 | } ) ; 86 | 87 | a = [ 'Alice' , 'Bob' , 'Charlie' ] ; 88 | expect( wildDotPath.getPaths( a , '*' ) ).to.equal( [ 89 | "0" , 90 | "1" , 91 | "2" 92 | ] ) ; 93 | expect( wildDotPath.getPathValueMap( a , '*' ) ).to.equal( { 94 | "0": 'Alice' , 95 | "1": 'Bob' , 96 | "2": 'Charlie' 97 | } ) ; 98 | } ) ; 99 | 100 | it( ".get()/.getPaths()/.getPathValueMap() on object of objects" , () => { 101 | var a = { 102 | embedded: { 103 | father: { firstName: 'Bobby' , lastName: 'Fischer' } , 104 | godfather: { firstName: 'Bob' , lastName: 'Ross' } , 105 | friend: { firstName: 'Joe' , lastName: 'Doe' } 106 | } 107 | } ; 108 | 109 | expect( wildDotPath.get( a , 'embedded.*' ) ).to.equal( [ 110 | { firstName: 'Bobby' , lastName: 'Fischer' } , 111 | { firstName: 'Bob' , lastName: 'Ross' } , 112 | { firstName: 'Joe' , lastName: 'Doe' } 113 | ] ) ; 114 | expect( wildDotPath.get( a , 'embedded.*.firstName' ) ).to.equal( [ 'Bobby' , 'Bob' , 'Joe' ] ) ; 115 | expect( wildDotPath.get( a , 'embedded.*.lastName' ) ).to.equal( [ 'Fischer' , 'Ross' , 'Doe' ] ) ; 116 | expect( wildDotPath.get( a , 'embedded.*.unexistant' ) ).to.equal( [ undefined , undefined , undefined ] ) ; 117 | expect( wildDotPath.get( a , 'unexistant.*.unexistant' ) ).to.equal( [] ) ; 118 | expect( wildDotPath.get( a , 'embedded.godfather.firstName' ) ).to.equal( [ 'Bob' ] ) ; 119 | 120 | expect( wildDotPath.getPaths( a , 'embedded.*.firstName' ) ).to.equal( [ 121 | "embedded.father.firstName" , 122 | "embedded.godfather.firstName" , 123 | "embedded.friend.firstName" 124 | ] ) ; 125 | 126 | expect( wildDotPath.getPathValueMap( a , 'embedded.*.firstName' ) ).to.equal( { 127 | "embedded.father.firstName": 'Bobby' , 128 | "embedded.godfather.firstName": 'Bob' , 129 | "embedded.friend.firstName": 'Joe' 130 | } ) ; 131 | } ) ; 132 | 133 | it( ".set() on array of objects" , () => { 134 | var a = { 135 | embedded: [ 136 | { firstName: 'Bobby' , lastName: 'Fischer' } , 137 | { firstName: 'Bob' , lastName: 'Ross' } , 138 | { firstName: 'Joe' , lastName: 'Doe' } 139 | ] 140 | } ; 141 | 142 | expect( wildDotPath.set( a , 'embedded.*.lastName' , 'Doe' ) ).to.be( 3 ) ; 143 | expect( a ).to.equal( { 144 | embedded: [ 145 | { firstName: 'Bobby' , lastName: 'Doe' } , 146 | { firstName: 'Bob' , lastName: 'Doe' } , 147 | { firstName: 'Joe' , lastName: 'Doe' } 148 | ] 149 | } ) ; 150 | 151 | expect( wildDotPath.set( a , 'embedded.*.age' , 42 ) ).to.be( 3 ) ; 152 | expect( a ).to.equal( { 153 | embedded: [ 154 | { firstName: 'Bobby' , lastName: 'Doe' , age: 42 } , 155 | { firstName: 'Bob' , lastName: 'Doe' , age: 42 } , 156 | { firstName: 'Joe' , lastName: 'Doe' , age: 42 } 157 | ] 158 | } ) ; 159 | 160 | expect( wildDotPath.set( { embedded: [] } , 'embedded.*.age' , 42 ) ).to.be( 0 ) ; 161 | 162 | // Should not throw, but should do nothing 163 | expect( wildDotPath.set( 1 , 'embedded.*.lastName' , 'Doe' ) ).to.be( 0 ) ; 164 | expect( wildDotPath.set( 1 , 'embedded' , 'Doe' ) ).to.be( 0 ) ; 165 | 166 | a = [ { name: 'Alice' } , { name: 'Bob' } , { name: 'Charlie' } ] ; 167 | wildDotPath.set( a , '*.name' , 'Bobby' ) ; 168 | //expect( wildDotPath.set( a , '*' , 'Bobby' ) ).to.be( 3 ) ; 169 | expect( a ).to.equal( [ { name: 'Bobby' } , { name: 'Bobby' } , { name: 'Bobby' } ] ) ; 170 | 171 | a = [ 'Alice' , 'Bob' , 'Charlie' ] ; 172 | wildDotPath.set( a , '*' , 'Bobby' ) ; 173 | //expect( wildDotPath.set( a , '*' , 'Bobby' ) ).to.be( 3 ) ; 174 | expect( a ).to.equal( [ 'Bobby' , 'Bobby' , 'Bobby' ] ) ; 175 | } ) ; 176 | 177 | it( ".set() should pave objects" , () => { 178 | var a = { 179 | embedded: [ 180 | { firstName: 'Bobby' , lastName: 'Fischer' } , 181 | { firstName: 'Bob' , lastName: 'Ross' } , 182 | { firstName: 'Joe' , lastName: 'Doe' } 183 | ] 184 | } ; 185 | 186 | wildDotPath.set( a , 'embedded.*.data.age' , 42 ) ; 187 | expect( a ).to.equal( { 188 | embedded: [ 189 | { firstName: 'Bobby' , lastName: 'Fischer' , data: { age: 42 } } , 190 | { firstName: 'Bob' , lastName: 'Ross' , data: { age: 42 } } , 191 | { firstName: 'Joe' , lastName: 'Doe' , data: { age: 42 } } 192 | ] 193 | } ) ; 194 | } ) ; 195 | 196 | it( ".define() on array of objects" , () => { 197 | var a = { 198 | embedded: [ 199 | { firstName: 'Bobby' , lastName: 'Fischer' } , 200 | { firstName: 'Jack' } , 201 | { firstName: 'Bob' , lastName: 'Ross' } , 202 | { firstName: 'Joe' } 203 | ] 204 | } ; 205 | 206 | expect( wildDotPath.define( a , 'embedded.*.lastName' , 'Doe' ) ).to.be( 2 ) ; 207 | expect( a ).to.equal( { 208 | embedded: [ 209 | { firstName: 'Bobby' , lastName: 'Fischer' } , 210 | { firstName: 'Jack' , lastName: 'Doe' } , 211 | { firstName: 'Bob' , lastName: 'Ross' } , 212 | { firstName: 'Joe' , lastName: 'Doe' } 213 | ] 214 | } ) ; 215 | } ) ; 216 | 217 | it( ".delete() on array of objects" , () => { 218 | var a = { 219 | embedded: [ 220 | { firstName: 'Bobby' , lastName: 'Fischer' } , 221 | { firstName: 'Jack' } , 222 | { firstName: 'Bob' , lastName: 'Ross' } , 223 | { firstName: 'Joe' } 224 | ] 225 | } ; 226 | 227 | expect( wildDotPath.delete( a , 'embedded.*.lastName' ) ).to.be( 2 ) ; 228 | expect( a ).to.equal( { 229 | embedded: [ 230 | { firstName: 'Bobby' } , 231 | { firstName: 'Jack' } , 232 | { firstName: 'Bob' } , 233 | { firstName: 'Joe' } 234 | ] 235 | } ) ; 236 | } ) ; 237 | 238 | it( ".inc()/.dec() on array of objects" , () => { 239 | var a = { 240 | embedded: [ 241 | { firstName: 'Bobby' , lastName: 'Fischer' , data: { age: 51 } } , 242 | { firstName: 'Bob' , lastName: 'Ross' , data: { age: 34 } } , 243 | { firstName: 'Joe' , lastName: 'Doe' , data: { age: 42 } } 244 | ] 245 | } ; 246 | 247 | expect( wildDotPath.inc( a , 'embedded.*.data.age' ) ).to.be( 3 ) ; 248 | expect( a ).to.equal( { 249 | embedded: [ 250 | { firstName: 'Bobby' , lastName: 'Fischer' , data: { age: 52 } } , 251 | { firstName: 'Bob' , lastName: 'Ross' , data: { age: 35 } } , 252 | { firstName: 'Joe' , lastName: 'Doe' , data: { age: 43 } } 253 | ] 254 | } ) ; 255 | 256 | expect( wildDotPath.dec( a , 'embedded.*.data.age' ) ).to.be( 3 ) ; 257 | expect( a ).to.equal( { 258 | embedded: [ 259 | { firstName: 'Bobby' , lastName: 'Fischer' , data: { age: 51 } } , 260 | { firstName: 'Bob' , lastName: 'Ross' , data: { age: 34 } } , 261 | { firstName: 'Joe' , lastName: 'Doe' , data: { age: 42 } } 262 | ] 263 | } ) ; 264 | } ) ; 265 | 266 | it( ".append()/.prepend() on array of objects" , () => { 267 | var a = { 268 | embedded: [ 269 | { name: 'Jack' } , 270 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' ] } , 271 | { name: 'Bob' , friends: [ 'Clara' ] } , 272 | { name: 'Joe' , friends: [] } 273 | ] 274 | } ; 275 | 276 | expect( wildDotPath.append( a , 'embedded.*.friends' , 'Joana' ) ).to.be( 4 ) ; 277 | expect( a ).to.equal( { 278 | embedded: [ 279 | { name: 'Jack' , friends: [ 'Joana' ] } , 280 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' ] } , 281 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' ] } , 282 | { name: 'Joe' , friends: [ 'Joana' ] } 283 | ] 284 | } ) ; 285 | 286 | a = { 287 | embedded: [ 288 | { name: 'Jack' } , 289 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' ] } , 290 | { name: 'Bob' , friends: [ 'Clara' ] } , 291 | { name: 'Joe' , friends: [] } 292 | ] 293 | } ; 294 | 295 | expect( wildDotPath.prepend( a , 'embedded.*.friends' , 'Joana' ) ).to.be( 4 ) ; 296 | expect( a ).to.equal( { 297 | embedded: [ 298 | { name: 'Jack' , friends: [ 'Joana' ] } , 299 | { name: 'Bobby' , friends: [ 'Joana' , 'Jack' , 'Nickie' ] } , 300 | { name: 'Bob' , friends: [ 'Joana' , 'Clara' ] } , 301 | { name: 'Joe' , friends: [ 'Joana' ] } 302 | ] 303 | } ) ; 304 | } ) ; 305 | 306 | it( ".autoPush() on array of objects" , () => { 307 | var a = { 308 | embedded: [ 309 | { name: 'Jack' } , 310 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' ] } , 311 | { name: 'Bob' , friends: [ 'Clara' ] } , 312 | { name: 'Joe' , friends: [] } 313 | ] 314 | } ; 315 | 316 | expect( wildDotPath.autoPush( a , 'embedded.*.friends' , 'Joana' ) ).to.be( 4 ) ; 317 | expect( a ).to.equal( { 318 | embedded: [ 319 | { name: 'Jack' , friends: 'Joana' } , 320 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' ] } , 321 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' ] } , 322 | { name: 'Joe' , friends: [ 'Joana' ] } 323 | ] 324 | } ) ; 325 | 326 | expect( wildDotPath.autoPush( a , 'embedded.*.friends' , 'Nina' ) ).to.be( 4 ) ; 327 | expect( a ).to.equal( { 328 | embedded: [ 329 | { name: 'Jack' , friends: [ 'Joana' , 'Nina' ] } , 330 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' , 'Nina' ] } , 331 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' , 'Nina' ] } , 332 | { name: 'Joe' , friends: [ 'Joana' , 'Nina' ] } 333 | ] 334 | } ) ; 335 | } ) ; 336 | 337 | it( ".concat()/.insert() on array of objects" , () => { 338 | var a = { 339 | embedded: [ 340 | { name: 'Jack' } , 341 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' ] } , 342 | { name: 'Bob' , friends: [ 'Clara' ] } , 343 | { name: 'Joe' , friends: [] } 344 | ] 345 | } ; 346 | 347 | expect( wildDotPath.concat( a , 'embedded.*.friends' , [ 'Joana' , 'Kate' ] ) ).to.be( 4 ) ; 348 | expect( a ).to.equal( { 349 | embedded: [ 350 | { name: 'Jack' , friends: [ 'Joana' , 'Kate' ] } , 351 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' , 'Kate' ] } , 352 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' , 'Kate' ] } , 353 | { name: 'Joe' , friends: [ 'Joana' , 'Kate' ] } 354 | ] 355 | } ) ; 356 | 357 | a = { 358 | embedded: [ 359 | { name: 'Jack' } , 360 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' ] } , 361 | { name: 'Bob' , friends: [ 'Clara' ] } , 362 | { name: 'Joe' , friends: [] } 363 | ] 364 | } ; 365 | 366 | expect( wildDotPath.insert( a , 'embedded.*.friends' , [ 'Joana' , 'Kate' ] ) ).to.be( 4 ) ; 367 | expect( a ).to.equal( { 368 | embedded: [ 369 | { name: 'Jack' , friends: [ 'Joana' , 'Kate' ] } , 370 | { name: 'Bobby' , friends: [ 'Joana' , 'Kate' , 'Jack' , 'Nickie' ] } , 371 | { name: 'Bob' , friends: [ 'Joana' , 'Kate' , 'Clara' ] } , 372 | { name: 'Joe' , friends: [ 'Joana' , 'Kate' ] } 373 | ] 374 | } ) ; 375 | } ) ; 376 | 377 | it( "multiple wildcards in the same path" , () => { 378 | var a = { 379 | users: [ 380 | { name: 'Jack' , friends: [ 'Joana' , 'Kate' ] } , 381 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' , 'Kate' ] } , 382 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' , 'Kate' ] } , 383 | { name: 'Joe' , friends: [ 'Joana' , 'Kate' ] } 384 | ] 385 | } ; 386 | 387 | expect( wildDotPath.get( a , 'users.*' ) ).to.equal( [ 388 | { name: 'Jack' , friends: [ 'Joana' , 'Kate' ] } , 389 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' , 'Kate' ] } , 390 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' , 'Kate' ] } , 391 | { name: 'Joe' , friends: [ 'Joana' , 'Kate' ] } 392 | ] ) ; 393 | expect( wildDotPath.get( a , 'users.*.friends.*' ) ).to.equal( [ 394 | 'Joana' , 'Kate' , 'Jack' , 'Nickie' , 'Joana' , 'Kate' , 'Clara' , 'Joana' , 'Kate' , 'Joana' , 'Kate' 395 | ] ) ; 396 | 397 | a = [ 398 | { name: 'Jack' , friends: [ 'Joana' , 'Kate' ] } , 399 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' , 'Kate' ] } , 400 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' , 'Kate' ] } , 401 | { name: 'Joe' , friends: [ 'Joana' , 'Kate' ] } 402 | ] ; 403 | 404 | expect( wildDotPath.get( a , '*' ) ).to.equal( [ 405 | { name: 'Jack' , friends: [ 'Joana' , 'Kate' ] } , 406 | { name: 'Bobby' , friends: [ 'Jack' , 'Nickie' , 'Joana' , 'Kate' ] } , 407 | { name: 'Bob' , friends: [ 'Clara' , 'Joana' , 'Kate' ] } , 408 | { name: 'Joe' , friends: [ 'Joana' , 'Kate' ] } 409 | ] ) ; 410 | expect( wildDotPath.get( a , '*.friends.*' ) ).to.equal( [ 411 | 'Joana' , 'Kate' , 'Jack' , 'Nickie' , 'Joana' , 'Kate' , 'Clara' , 'Joana' , 'Kate' , 'Joana' , 'Kate' 412 | ] ) ; 413 | } ) ; 414 | } ) ; 415 | 416 | -------------------------------------------------------------------------------- /wfm.json: -------------------------------------------------------------------------------- 1 | { 2 | "prepare": {} 3 | } 4 | --------------------------------------------------------------------------------