├── .todo.txt
├── report.txt
├── todo.txt.bak
├── todo.txt
├── done.txt
└── config
├── wfm.json
├── log
└── README
├── SECURITY.md
├── .gitignore
├── .npmignore
├── LICENSE
├── package.json
├── lib
├── browser.js
├── tree.js
├── lazy.js
├── arrayLike.js
├── clone.js
├── diff.js
├── dotPath.js
├── path.js
├── wildDotPath.js
├── mask.js
└── extend.js
├── sample
├── garbageStringObject.js
├── stringFlatObject.js
├── bigFlatObject.js
├── bigDeepObject.js
└── sample1.json
├── test
├── lazy-test.js
├── arrayLike-test.js
├── diff-test.js
├── clone-test.js
├── mask-test.js
├── wildDotPath-test.js
└── dotPath-test.js
├── Makefile
├── bench
└── path-vs-dotPath.js
├── .eslintrc.js
├── CHANGELOG
├── README.md
├── documentation.md
└── browser
└── tree-kit.min.js
/.todo.txt/report.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/wfm.json:
--------------------------------------------------------------------------------
1 | {
2 | "prepare": {}
3 | }
4 |
--------------------------------------------------------------------------------
/log/README:
--------------------------------------------------------------------------------
1 | This force git to create this folder.
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/.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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/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/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 |
--------------------------------------------------------------------------------
/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: '