├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .todo.txt
├── config
├── done.txt
├── report.txt
├── todo.txt
└── todo.txt.bak
├── CHANGELOG
├── LICENSE
├── Makefile
├── README.md
├── SECURITY.md
├── bench
└── path-vs-dotPath.js
├── browser
├── tree-kit.js
└── tree-kit.min.js
├── documentation.md
├── lib
├── arrayLike.js
├── browser.js
├── clone.js
├── diff.js
├── dotPath.js
├── extend.js
├── lazy.js
├── mask.js
├── path.js
├── tree.js
└── wildDotPath.js
├── log
└── README
├── package.json
├── sample
├── bigDeepObject.js
├── bigFlatObject.js
├── garbageStringObject.js
├── sample1.json
└── stringFlatObject.js
├── test
├── arrayLike-test.js
├── clone-test.js
├── diff-test.js
├── dotPath-test.js
├── extend-test.js
├── lazy-test.js
├── mask-test.js
├── path-test.js
└── wildDotPath-test.js
└── wfm.json
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | 'root': true ,
3 | 'env': {
4 | 'browser': true ,
5 | 'node': true ,
6 | 'es6': true ,
7 | 'es2022': true
8 | } ,
9 | 'parserOptions': {
10 | 'ecmaVersion': 2022
11 | } ,
12 | 'extends': [ 'eslint:recommended' ] ,
13 | 'ignorePatterns': [ "*.min.js" ] ,
14 | 'rules': {
15 |
16 | /*
17 | Bad code -- detect anything that can be broken or lead to bugs
18 | */
19 |
20 |
21 |
22 | 'strict': [ 'error' , 'global' ] ,
23 | 'unicode-bom': [ 'error' , 'never' ] ,
24 | 'radix': 'error' ,
25 | 'eqeqeq': 'error' ,
26 | 'consistent-return': 'off' ,
27 | 'valid-typeof': 'error' ,
28 | 'no-unneeded-ternary': 'error' ,
29 | 'no-unused-vars': 'warn' , // During development phase, it's boring to clean unused var since they can be used later
30 | 'no-lonely-if': 'off' , // Can hurt semantic programming
31 | 'no-nested-ternary': 'off' , // Now I use the streamlined ternary operator a lot
32 | 'no-shadow': 'error' ,
33 | 'no-shadow-restricted-names': 'error' ,
34 | 'require-atomic-updates': 'off' , // check for possible race condition on assignment, interesting but too nitpicky
35 |
36 |
37 |
38 | /*
39 | Code preferences
40 | */
41 |
42 |
43 |
44 | 'prefer-arrow-callback': 'error' ,
45 | 'prefer-spread': 'warn' ,
46 | 'prefer-rest-params': 'warn' ,
47 | 'no-control-regex': 'off' , // because thing like \x00 are considered like a control even if escaped...
48 | 'no-fallthrough': 'off' ,
49 | 'no-empty': [ 'error' , {
50 | 'allowEmptyCatch': true
51 | } ] ,
52 |
53 |
54 |
55 | /*
56 | Coding styles -- cosmetic rules and opinionated preferences
57 | */
58 |
59 |
60 |
61 | // Indent & spaces (general)
62 | 'indent': [ 'error' , 'tab' , {
63 | 'SwitchCase': 1 ,
64 | 'MemberExpression': 1 ,
65 | 'flatTernaryExpressions': true
66 | } ] ,
67 | 'newline-per-chained-call': 'off',
68 | 'no-multi-spaces': 'off' ,
69 | 'block-spacing': 'error' ,
70 | 'comma-spacing': [ 'error' , {
71 | 'before': true ,
72 | 'after': true
73 | } ] ,
74 | 'no-whitespace-before-property': 'error' ,
75 | 'space-before-blocks': 'error' ,
76 | 'space-before-function-paren': [ 'error' , {
77 | 'anonymous': 'never',
78 | 'named': 'never',
79 | 'asyncArrow': 'always'
80 | } ] ,
81 | 'space-infix-ops': 'error' ,
82 | 'space-unary-ops': [ 'error' , {
83 | 'words': true ,
84 | 'nonwords': true ,
85 | 'overrides': {
86 | //'-': false ,
87 | }
88 | } ] ,
89 | 'space-in-parens': [ 'error' , 'always' , {
90 | 'exceptions': [ 'empty' ]
91 | } ] ,
92 | 'no-trailing-spaces': 'error' ,
93 | 'switch-colon-spacing': [ 'error' , {
94 | 'after': true ,
95 | 'before': true
96 | } ] ,
97 | 'arrow-spacing': 'error' ,
98 | 'rest-spread-spacing': [ 'error' , 'always' ] ,
99 | /* Troublesome with commented line of code
100 | 'spaced-comment': [ 'error' , 'always' , {
101 | 'line': {
102 | 'markers': [ '/' ],
103 | 'exceptions': [ '-', '*', '/' ]
104 | } ,
105 | 'block': {
106 | 'exceptions': [ '*' ] ,
107 | 'balanced': true
108 | }
109 | } ] ,
110 | */
111 |
112 |
113 | // Semi-colon
114 | 'semi': [ 'error' , 'always' ] ,
115 | 'semi-style': [ 'error' , 'last' ] ,
116 | 'semi-spacing': [ 'error' , {
117 | 'before': true ,
118 | 'after': true
119 | } ] ,
120 |
121 | // Objects
122 | 'key-spacing': [ 'error' , {
123 | 'beforeColon': false ,
124 | 'afterColon': true ,
125 | 'mode': 'strict'
126 | } ] ,
127 | 'object-curly-newline': [ 'error' , {
128 | 'ObjectExpression' : {
129 | 'consistent': true ,
130 | 'minProperties': 4
131 | } ,
132 | 'ObjectPattern' : {
133 | // object destructuring assigment
134 | 'consistent': true ,
135 | 'minProperties': 8
136 | }
137 | } ] ,
138 | 'object-curly-spacing': [ 'error' , 'always' ] ,
139 | 'object-property-newline': [ 'error' , { 'allowMultiplePropertiesPerLine': true } ] ,
140 |
141 |
142 | // Arrays
143 | 'array-bracket-newline': [ 'error' , 'consistent' ] ,
144 | //'array-element-newline': [ 'error' , { 'multiline': true , 'minItems': 5 } ] ,
145 | 'array-bracket-spacing': [ 'error' , 'always' ],
146 |
147 | 'brace-style': [ 'error' , 'stroustrup' , {
148 | 'allowSingleLine': true
149 | } ] ,
150 |
151 |
152 | // Misc style
153 | 'no-else-return': 'warn' ,
154 | 'comma-dangle': [ 'error' , 'never' ] ,
155 | 'quotes': 'off' ,
156 | 'camelcase': 'warn' ,
157 |
158 |
159 |
160 | /*
161 | Method limitation
162 | */
163 |
164 |
165 |
166 | 'no-console': 'off'
167 | }
168 | } ;
169 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Specific #
2 | ############
3 |
4 | *.local.*
5 | *.local
6 | *.log
7 | *.html.gz
8 | *.css.gz
9 | *.js.gz
10 | .spellcast
11 | build
12 | _build
13 | _templates
14 | _static
15 |
16 |
17 | # gitignore / Node.gitignore #
18 | ##############################
19 | lib-cov
20 | lcov.info
21 | *.seed
22 | *.log
23 | *.csv
24 | *.dat
25 | *.out
26 | *.pid
27 | *.gz
28 |
29 | pids
30 | logs
31 | results
32 | build
33 | .grunt
34 | package-lock.json
35 |
36 | node_modules
37 |
38 |
39 | # OS generated files #
40 | ######################
41 | .DS_Store
42 | .DS_Store?
43 | ._*
44 | .Spotlight-V100
45 | .Trashes
46 | Icon?
47 | ehthumbs.db
48 | Thumbs.db
49 |
50 |
51 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | # +++ .gitignore
2 | # Specific #
3 | ############
4 |
5 | *.local.*
6 | *.local
7 | *.log
8 | *.html.gz
9 | *.css.gz
10 | *.js.gz
11 | .spellcast
12 | build
13 | _build
14 | _templates
15 | _static
16 |
17 |
18 | # gitignore / Node.gitignore #
19 | ##############################
20 | lib-cov
21 | lcov.info
22 | *.seed
23 | *.log
24 | *.csv
25 | *.dat
26 | *.out
27 | *.pid
28 | *.gz
29 |
30 | pids
31 | logs
32 | results
33 | build
34 | .grunt
35 | package-lock.json
36 |
37 | node_modules
38 |
39 |
40 | # OS generated files #
41 | ######################
42 | .DS_Store
43 | .DS_Store?
44 | ._*
45 | .Spotlight-V100
46 | .Trashes
47 | Icon?
48 | ehthumbs.db
49 | Thumbs.db
50 |
51 |
52 |
53 | # --- .gitignore
54 | test
55 | log
56 | sample
57 | wfm.json
58 | builder
59 | test/
60 | doc/
61 | sample/
62 | builder/
63 |
--------------------------------------------------------------------------------
/.todo.txt/config:
--------------------------------------------------------------------------------
1 | # === EDIT FILE LOCATIONS BELOW ===
2 |
3 | PROJECT_NAME='Tree-kit'
4 |
5 | echo -ne "\n\t\e[0;35m~~> \e[1;35m$PROJECT_NAME\e[0;35m todo-list <~~\e[0m\n\n"
6 |
7 | # My config
8 | export TODOTXT_DEFAULT_ACTION=ls
9 |
10 | # Your todo.txt directory
11 | export TODO_DIR=$(dirname "$TODOTXT_CFG_FILE")
12 |
13 | # Your todo/done/report.txt locations
14 | export TODO_FILE="$TODO_DIR/todo.txt"
15 | export DONE_FILE="$TODO_DIR/done.txt"
16 | export REPORT_FILE="$TODO_DIR/report.txt"
17 |
18 | # You can customize your actions directory location
19 | #export TODO_ACTIONS_DIR="$HOME/.todo.actions.d"
20 |
21 | # == EDIT FILE LOCATIONS ABOVE ===
22 |
23 | # === COLOR MAP ===
24 |
25 | ## Text coloring and formatting is done by inserting ANSI escape codes.
26 | ## If you have re-mapped your color codes, or use the todo.txt
27 | ## output in another output system (like Conky), you may need to
28 | ## over-ride by uncommenting and editing these defaults.
29 | ## If you change any of these here, you also need to uncomment
30 | ## the defaults in the COLORS section below. Otherwise, todo.txt
31 | ## will still use the defaults!
32 |
33 | # export BLACK='\\033[0;30m'
34 | # export RED='\\033[0;31m'
35 | # export GREEN='\\033[0;32m'
36 | # export BROWN='\\033[0;33m'
37 | # export BLUE='\\033[0;34m'
38 | # export PURPLE='\\033[0;35m'
39 | # export CYAN='\\033[0;36m'
40 | # export LIGHT_GREY='\\033[0;37m'
41 | # export DARK_GREY='\\033[1;30m'
42 | # export LIGHT_RED='\\033[1;31m'
43 | # export LIGHT_GREEN='\\033[1;32m'
44 | # export YELLOW='\\033[1;33m'
45 | # export LIGHT_BLUE='\\033[1;34m'
46 | # export LIGHT_PURPLE='\\033[1;35m'
47 | # export LIGHT_CYAN='\\033[1;36m'
48 | # export WHITE='\\033[1;37m'
49 | # export DEFAULT='\\033[0m'
50 |
51 | # === COLORS ===
52 |
53 | ## Uncomment and edit to override these defaults.
54 | ## Reference the constants from the color map above,
55 | ## or use $NONE to disable highlighting.
56 | #
57 | # Priorities can be any upper-case letter.
58 | # A,B,C are highlighted; you can add coloring for more.
59 | #
60 | # export PRI_A=$YELLOW # color for A priority
61 | # export PRI_B=$GREEN # color for B priority
62 | # export PRI_C=$LIGHT_BLUE # color for C priority
63 | # export PRI_D=... # define your own
64 | # export PRI_X=$WHITE # color unless explicitly defined
65 |
66 | # There is highlighting for tasks that have been done,
67 | # but haven't been archived yet.
68 | #
69 | # export COLOR_DONE=$LIGHT_GREY
70 |
71 | # There is highlighting for projects and contexts.
72 | #
73 | # export COLOR_PROJECT=$RED
74 | # export COLOR_CONTEXT=$RED
75 |
76 | # === BEHAVIOR ===
77 |
78 | ## customize list output
79 | #
80 | # TODOTXT_SORT_COMMAND will filter after line numbers are
81 | # inserted, but before colorization, and before hiding of
82 | # priority, context, and project.
83 | #
84 | # export TODOTXT_SORT_COMMAND='env LC_COLLATE=C sort -f -k2'
85 |
86 | # TODOTXT_FINAL_FILTER will filter list output after colorization,
87 | # priority hiding, context hiding, and project hiding. That is,
88 | # just before the list output is displayed.
89 | #
90 | # export TODOTXT_FINAL_FILTER='cat'
91 |
--------------------------------------------------------------------------------
/.todo.txt/done.txt:
--------------------------------------------------------------------------------
1 | x 2014-11-04 +extend deep:true bug with function, there are copied as undefined @bug
2 | x 2014-11-04 +extend document 'deepFunc' option @doc
3 | x 2014-12-23 +extend finish the 'descriptor' option: what to do with 'deep' when a property is a getter/setter? @code
4 | x 2015-01-09 +extend options 'descriptor': copy property descriptor as well, using Object.getOwnPropertyDescriptor() & Object.defineProperty() @code
5 | x 2015-01-09 +extend options 'nonEnum': get non-enumerable properties as well, using Object.getOwnPropertyNames() @code
6 | x 2015-01-10 +extend still trouble with getter: direct access rather than descriptor.value on the /!\ mark @bug
7 | x 2015-01-10 +extend document 'circular' option @doc
8 | x 2015-01-10 +extend document 'maxDepth' option @doc
9 |
--------------------------------------------------------------------------------
/.todo.txt/report.txt:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/cronvel/tree-kit/881c286f9cf9342fecd3ac04a8f31956949058d3/.todo.txt/report.txt
--------------------------------------------------------------------------------
/.todo.txt/todo.txt:
--------------------------------------------------------------------------------
1 | +defineLazyProperty document it @doc
2 | do not mix 'you' and 'we' in the @doc
3 | +path document it @doc
4 | (B) +path path as array should support array walking @code
5 |
--------------------------------------------------------------------------------
/.todo.txt/todo.txt.bak:
--------------------------------------------------------------------------------
1 | +defineLazyProperty document it @doc
2 | x 2015-01-10 +extend document 'maxDepth' option @doc
3 | do not mix 'you' and 'we' in the @doc
4 |
--------------------------------------------------------------------------------
/CHANGELOG:
--------------------------------------------------------------------------------
1 |
2 | v0.8.8
3 | ------
4 |
5 | Fix wildDotPath when the wildcard is at the end of the path
6 |
7 |
8 | v0.8.7
9 | ------
10 |
11 | New array-like toolbox
12 |
13 |
14 | v0.8.6
15 | ------
16 |
17 | Expose .toPathArray() in dotPath and wildDotPath
18 |
19 |
20 | v0.8.5
21 | ------
22 |
23 | dotPath/wildDotPath .delete() minor fix
24 |
25 |
26 | v0.8.4
27 | ------
28 |
29 | wildDotPath now has all things in dotPath
30 |
31 |
32 | v0.8.3
33 | ------
34 |
35 | Fix missing require for new wildDotPath
36 |
37 |
38 | v0.8.2
39 | ------
40 |
41 | New: wildDotPath.get() and wildDotPath.getPathValue() (more methods will be added later, e.g. wildDotPath.set())
42 |
43 |
44 | v0.7.5
45 | ------
46 |
47 | Fix prototype pollution in .extend() when the 'unflat' option is set
48 |
49 |
50 | v0.7.4
51 | ------
52 |
53 | dotPath: empty path part support
54 |
55 |
56 | v0.7.3
57 | ------
58 |
59 | .extend() option 'mask' now supports a number, the rank at which the masking starts
60 |
61 |
62 | v0.7.2
63 | ------
64 |
65 | .extend(): fix the 'preserve' option bug when replacing with an object. New option 'mask' that is the revert of 'preserve': only update an object, but do not create new keys
66 |
67 |
68 | v0.7.1
69 | ------
70 |
71 | path/dotPath with path in array mode now checks that there is no object in the array
72 |
73 |
74 | v0.7.0
75 | ------
76 |
77 | BREAKING CHANGE -- .path()/.dotPath(): drop the function's subtree support, fix prototype pollution
78 |
79 |
80 | v0.6.2
81 | ------
82 |
83 | .clone() with Date support
84 |
85 |
86 | v0.6.1
87 | ------
88 |
89 | New: .dotPath.*(): a faster alternative to .path.*(), supporting only dot-separated path
90 |
91 |
92 | v0.6.0
93 | ------
94 |
95 | Breaking change: .extend() 'deepFilter' option is gone 'deepFilter.whitelist' has moved to 'deep' and 'deepFilter.blacklist' has moved to 'immutables'
96 |
97 |
98 | v0.5.27
99 | -------
100 |
101 | Fixed a vulnerability in .extend(), moved to ESLint, moved to Tea-Time builtin 'expect'
102 |
103 |
104 | v0.5.26
105 | -------
106 |
107 | New: Browser lib!
108 |
109 |
110 | v0.5.25
111 | -------
112 |
113 | tree.path -- new operations: concat and insert
114 |
115 |
116 | v0.5.24
117 | -------
118 |
119 | IMPORTANT BUGFIX: clone was not working well with arrays
120 |
121 |
122 | v0.5.23
123 | -------
124 |
125 | clone interface
126 |
127 |
128 | v0.5.22
129 | -------
130 |
131 | Tree.path.* now supports empty keys
132 |
133 |
134 | v0.5.21
135 | -------
136 |
137 | tree.path() now return undefined if the source object, well, is not an object (instead of throwing)
138 |
139 |
140 | v0.5.20
141 | -------
142 |
143 | Bugfix: make require( 'tree-kit/lib/path.js' ) works as expected (was encapsulating everything in a 'path' sub-object)
144 |
145 |
146 | v0.5.19
147 | -------
148 |
149 | New: tree.path.autoPush()
150 |
151 |
152 | v0.5.18
153 | -------
154 |
155 | tree.json was removed (get its own module: json-kit)
156 |
157 |
158 | v0.5.17
159 | -------
160 |
161 | json: parser run a bit faster
162 |
163 |
164 | v0.5.16
165 | -------
166 |
167 | json: parser run a bit faster
168 |
169 |
170 | v0.5.15
171 | -------
172 |
173 | json: parser is now working
174 |
175 |
176 | v0.5.14
177 | -------
178 |
179 | json.stringify() improvements
180 |
181 |
182 | v0.5.13
183 | -------
184 |
185 | "use strict" everywhere
186 |
187 |
188 | v0.5.12
189 | -------
190 |
191 | Bechmark: ubench v0.2.x ; "use strict" everywhere
192 |
193 |
194 | v0.5.11
195 | -------
196 |
197 | ubench benchmark
198 |
199 |
200 | v0.5.10
201 | -------
202 |
203 | json: some fixes
204 |
205 |
206 | v0.5.9
207 | ------
208 |
209 | json.stringify() is now on par with native JSON.stringify()!
210 |
211 |
212 | v0.5.8
213 | ------
214 |
215 | json utilities: wip
216 |
217 |
218 | v0.5.7
219 | ------
220 |
221 | New: tree.path.append() and tree.path.prepend()
222 |
223 |
224 | v0.5.6
225 | ------
226 |
227 | New feature: tree.path() now support the bracket syntax for arrays.
228 |
229 |
230 | v0.5.5
231 | ------
232 |
233 | Documentation: just added link to http://blog.soulserv.net/tag/tree-kit that points to tree-kit tutorials.
234 |
235 |
236 | v0.5.4
237 | ------
238 |
239 | path: all methods return the targeted object like path.get() does.
240 |
241 |
242 | v0.5.3
243 | ------
244 |
245 | New path.*() method: path.define(), like set, but only if the targeted item does not exist
246 |
247 |
248 | v0.5.2
249 | ------
250 |
251 | New path.*() methods: path.inc() & path.dec(), that increment and decrement values
252 |
253 |
254 | v0.5.1
255 | ------
256 |
257 | path.*():
258 | * tree.path.prototype can be used for inheritance, using Object.create( tree.path.prototype )
259 | * path.*() now supports path as array too (but it's still not done for array walking)
260 |
261 |
262 | v0.5.0
263 | ------
264 |
265 | path.*(): pseudo-element notation use '#' instead of ':'
266 |
267 |
268 | v0.4.3 - v0.4.4
269 | ---------------
270 |
271 | path.*() now support a semi-colon syntax for accessing arrays, featuring pseudo-element like :last and :next, etc.
272 |
273 |
274 | v0.4.2
275 | ------
276 |
277 | New: path submodule featuring path.get(), path.set() and path.delete(). It allows setting, getting deleting by a
278 | dot-separated path scheme.
279 |
280 |
281 | v0.4.1
282 | ------
283 |
284 | clone() have now its own module/file: clone.js.
285 |
286 |
287 | v0.4.0
288 | ------
289 |
290 | extend():
291 | * 'circular' & 'maxDepth' options finished, 'descriptor' option bugfix
292 |
293 | clone():
294 | * does not depend upon extend() anymore, it has been fully rewritten with optimization in mind
295 | * it does not use recursive function call but loops, which is super-efficient ;)
296 | * it now accepts a *circular* boolean argument, just like extend(), see the doc!
297 |
298 |
299 | v0.3.5
300 | ------
301 |
302 | Doc: table of content.
303 |
304 |
305 | v0.3.4
306 | ------
307 |
308 | New method: clone(), providing the best object-cloning facility that this lib can offer.
309 |
310 |
311 | v0.3.3
312 | ------
313 |
314 | extend()
315 | - 'nonEnum' option that copy non-enumerable properties as well (in conjunction with 'own')
316 | - 'descriptor' option that preserve property's descriptor
317 |
318 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2014 Cédric Ronvel
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | # User rules
5 |
6 | # The first rule is the default rule, when invoking "make" without argument...
7 | # Build every buildable things
8 | all: install doc browser
9 |
10 | # Just install things so it works, basicaly: it just performs a "npm install --production" ATM
11 | install: log/npm-install.log
12 |
13 | # Just install things so it works, basicaly: it just performs a "npm install" ATM
14 | dev-install: log/npm-dev-install.log
15 |
16 | # Build
17 | build: browser
18 |
19 | # Build the browser lib
20 | browser: browser/tree-kit.js browser/tree-kit.min.js
21 |
22 | # This run the JsHint & Mocha BDD test, display it to STDOUT & save it to log/mocha.log and log/jshint.log
23 | test: log/jshint.log log/mocha.log
24 |
25 | # This run the JsHint, display it to STDOUT & save it to log/jshint.log
26 | lint: log/jshint.log
27 |
28 | # This run the Mocha BDD test, display it to STDOUT & save it to log/mocha.log
29 | unit: log/mocha.log
30 |
31 | # Performs the browser tests
32 | browser-test: log/testling.log
33 |
34 | # This build the doc and README.md
35 | doc: README.md
36 |
37 | # This publish to NPM and push to Github, if we are on master branch only
38 | publish: log/npm-publish.log log/github-push.log
39 |
40 | # Clean temporary things, or things that can be automatically regenerated
41 | clean: clean-all
42 |
43 |
44 |
45 | # Variables
46 |
47 | MOCHA=mocha
48 | JSHINT=./node_modules/jshint/bin/jshint --verbose
49 | BROWSERIFY=./node_modules/.bin/browserify
50 | UGLIFY=./node_modules/.bin/uglifyjs
51 |
52 |
53 |
54 | # Files rules
55 |
56 | # Build the browser lib
57 | browser/tree-kit.js: lib/*.js
58 | ${BROWSERIFY} lib/browser.js -s treeKit -o browser/tree-kit.js
59 |
60 | # Build the browser minified lib
61 | browser/tree-kit.min.js: browser/tree-kit.js
62 | ${UGLIFY} browser/tree-kit.js -o browser/tree-kit.min.js -m
63 |
64 | # JsHint STDOUT test
65 | log/jshint.log: log/npm-dev-install.log lib/*.js test/*.js
66 | ${JSHINT} lib/*.js test/*.js | tee log/jshint.log ; exit $${PIPESTATUS[0]}
67 |
68 | # Mocha BDD STDOUT test
69 | log/mocha.log: log/npm-dev-install.log lib/*.js test/*.js
70 | ${MOCHA} test/*.js -R spec | tee log/mocha.log ; exit $${PIPESTATUS[0]}
71 |
72 | # README
73 | README.md: documentation.md
74 | cat documentation.md > README.md
75 |
76 | # Mocha Markdown BDD spec
77 | bdd-spec.md: log/npm-dev-install.log lib/*.js test/*.js
78 | ${MOCHA} test/*.js -R markdown > bdd-spec.md
79 |
80 | # Upgrade version in package.json
81 | log/upgrade-package.log: lib/*.js test/*.js documentation.md
82 | npm version patch -m "Upgrade package.json version to %s" | tee log/upgrade-package.log ; exit $${PIPESTATUS[0]}
83 |
84 | # Publish to NPM
85 | log/npm-publish.log: check-if-master-branch log/upgrade-package.log
86 | npm publish | tee log/npm-publish.log ; exit $${PIPESTATUS[0]}
87 |
88 | # Push to Github/master
89 | log/github-push.log: lib/*.js test/*.js package.json
90 | #'npm version patch' create the git tag by itself...
91 | #git tag v`cat package.json | grep version | sed -r 's/.*"([0-9.]*)".*/\1/'`
92 | git push origin master --tags | tee log/github-push.log ; exit $${PIPESTATUS[0]}
93 |
94 | # NPM install
95 | log/npm-install.log: package.json
96 | npm install --production | tee log/npm-install.log ; exit $${PIPESTATUS[0]}
97 |
98 | # NPM install for developpement usage
99 | log/npm-dev-install.log: package.json
100 | npm install | tee log/npm-dev-install.log ; exit $${PIPESTATUS[0]}
101 |
102 |
103 |
104 | # PHONY rules
105 |
106 | .PHONY: clean-all check-if-master-branch
107 |
108 | # Delete files, mostly log and non-versioned files
109 | clean-all:
110 | rm -rf log/*.log README.md bdd-spec.md node_modules
111 |
112 | # This will fail if we are not on master branch (grep exit 1 if nothing found)
113 | check-if-master-branch:
114 | git branch | grep "^* master$$"
115 |
116 |
117 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | # Tree Kit
3 |
4 | This lib is a toolbox that provide functions to operate with nested `Object` structure.
5 | It features the best `.extend()` method, providing dozen of options that all others libs miss.
6 |
7 | * License: MIT
8 | * Current status: release candidate
9 | * Platform: Node.js only (browser support is planned)
10 |
11 | Some tutorials are available at [blog.soulserv.net/tag/tree-kit](http://blog.soulserv.net/tag/tree-kit/).
12 |
13 |
14 |
15 | # Install
16 |
17 | Use Node Package Manager:
18 |
19 | npm install tree-kit
20 |
21 |
22 |
23 | # Library references
24 |
25 | * [.extend()](#ref.extend): full-featured extend facility, copy, clone, extend
26 | * [.clone()](#ref.clone): clone any object
27 | * [.diff()](#ref.diff): report differences between two objects
28 |
29 |
30 |
31 | In all examples below, it is assumed that you have required the lib into the `tree` variable:
32 | ```js
33 | var tree = require( 'tree-kit' ) ;
34 | ```
35 |
36 |
37 |
38 |
39 | ## .extend( options , target , source1 , [source2] , [...] )
40 |
41 | * options `Object` extend options, it supports the properties:
42 | * own `boolean` only copy enumerable own properties from the sources
43 | * nonEnum `boolean` copy non-enumerable properties as well, works only with own:true
44 | * descriptor `boolean` preserve property's descriptor (i.e. writable, enumerable, configurable, get & set)
45 | * deep: `boolean` or `Array` or `Set`, if true perform a deep (recursive) extend, if it is an Array/Set of prototypes, only deep-copy
46 | objects of those prototypes (it is a replacement for deepFilter.whitelist which was removed in Tree Kit 0.6)
47 | * immutables: an Array/Set of immutable object's prototypes that are filtered out for deep-copy (it is a replacement
48 | for deepFilter.blacklist which was removed in Tree Kit 0.6)
49 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected
50 | (referenced), if false then nested object are blindly cloned
51 | * maxDepth `integer` used in conjunction with deep, when the max depth is reached an exception is raised, it defaults to 100
52 | when the 'circular' option is off, or defaults to null if 'circular' is on
53 | * move `boolean` move properties from the sources object to the target object (delete properties from the sources object)
54 | * preserve `boolean` existing properties in the target object will not be overwritten
55 | * nofunc `boolean` skip properties that are functions
56 | * deepFunc `boolean` in conjunction with 'deep', this will process sources functions like objects rather than
57 | copying/referencing them directly into the source (default behaviour), thus, the result will not be a function,
58 | it forces 'deep' options
59 | * proto `boolean` alter the target's prototype so that it matches the source's prototype.
60 | It forces option 'own'. Specifying multiple sources does not make sens here.
61 | * inherit `boolean` make the target inherit from the source (the target's prototype will be the source itself, not its prototype).
62 | It forces option 'own' and disable 'proto'. Specifying multiple sources does not make sens here.
63 | * skipRoot `boolean` prevent the prototype of the target **root** object from mutation.
64 | Only nested objects' prototype will be mutated.
65 | * flat `boolean|string` sources properties are copied in a way to produce a *flat* target, the target's key
66 | is the full path (separated by '.') of the source's key, also if a string is provided it will be used as
67 | the path separator
68 | * unflat `boolean|string` it is the opposite of 'flat': assuming that the sources are in the *flat* format,
69 | it expands all flat properties -- whose name are path with '.' as the separator -- deeply into the target,
70 | also if a string is provided it will be used as the path separator
71 | * target `Object` the target of the extend, properties will be copied to this object
72 | * source1 `Object` the source of the extend, properties will be copied from this object
73 | * ...
74 |
75 | This is a full-featured *extend* of an object with one or more source object.
76 |
77 | It is easily translated from jQuery-like *extend()*:
78 | * `extend( target , source )` translate into `tree.extend( null , target , source )`
79 | * `extend( true , target , source )` translate into `tree.extend( { deep: true } , target , source )`
80 |
81 | However, here we have full control over what will be extended and how.
82 |
83 | **All the options above are inactive by default**.
84 | You can pass null as argument #0 to get the default behaviour (= all options are inactive).
85 | So using the default behaviour, `tree.extend()` will copy all enumerable properties, and perform a shallow copy (a nested object
86 | is not cloned, it remains a reference of the original one).
87 |
88 | With the *deep* option, a deep copy is performed, so nested object are cloned too.
89 |
90 | The *own* option clone only owned properties from the sources, properties that are part of the source's prototype would not
91 | be copied/cloned.
92 |
93 | The *nonEnum* option will clone properties that are not enumerable.
94 |
95 | The *descriptor* option will preserve property's descriptor, e.g. if the source property is not writable and not enumerable,
96 | so will be the copied property.
97 |
98 | In case of a *getter* properties:
99 |
100 | * without the *descriptor* option, the getter function of the source object will be called, the return value will be put
101 | into the target property (so it lose its getter/setter behaviour)
102 | * with the *descriptor* option, the getter & setter function of the source object will be copied (but not called) into the target
103 | property: the getter/setter behaviour is preserved
104 |
105 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it.
106 | We can see this *circular* feature in action in [this example](#example.circular).
107 |
108 | Mixing *inherit* and *deep* provides a nice multi-level inheritance.
109 |
110 | With the *flat* option example:
111 | ```js
112 | var o = {
113 | one: 1,
114 | sub: {
115 | two: 2,
116 | three: 3
117 | }
118 | } ;
119 |
120 | var flatCopy = tree.extend( { flat: true } , {} , o ) ;
121 | ```
122 | ... it will produce:
123 | ```js
124 | {
125 | one: 1,
126 | "sub.two": 2,
127 | "sub.three": 3
128 | }
129 | ```
130 |
131 | By the way, the *unflat* option does the opposite, and thus can reverse this back to the original form.
132 |
133 | The *deepFilter* option is used when you do not want to clone some type of object.
134 | Let's say you want a deep copy except for `Buffer` objects, you simply want them to share the same reference:
135 | ```js
136 | var o = {
137 | one: '1' ,
138 | buf: new Buffer( "My buffer" ) ,
139 | subtree: {
140 | two: 2 ,
141 | three: 'THREE'
142 | }
143 | } ;
144 |
145 | // either
146 | var extended1 = tree.extend( { deep: true, deepFilter: { whitelist: [ Object.prototype ] } } , {} , o ) ;
147 | // or
148 | var extended2 = tree.extend( { deep: true, deepFilter: { blacklist: [ Buffer.prototype ] } } , {} , o ) ;
149 | ```
150 |
151 | Doing this, we have `o.buf === extended1.buf === extended2.buf`, and `o.subtree !== extended1.subtree !== extended2.subtree`.
152 |
153 |
154 |
155 |
156 | ## .clone( original , [circular] )
157 |
158 | * original `Object` the source object to clone
159 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected
160 | (referenced), if false then nested object are blindly cloned
161 |
162 | It returns a clone of the *original* object, providing the best object-cloning facility that this lib can offer.
163 |
164 | The clone produced are perfect independant copy **in 99% of use case**, but there is one big limitation:
165 | method that access variables in the parent's scope.
166 |
167 | The clone will share those variables with the *original* object, so they are not totally independant entity.
168 | Design pattern using closure to emulate *private member* (e.g. the revealing pattern) can cause trouble.
169 |
170 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it.
171 |
172 |
173 | Here is an example of this *circular* feature:
174 | ```js
175 | var o = {
176 | a: 'a',
177 | sub: {
178 | b: 'b'
179 | },
180 | sub2: {
181 | c: 'c'
182 | }
183 | } ;
184 |
185 | o.loop = o ;
186 | o.sub.loop = o ;
187 | o.subcopy = o.sub ;
188 | o.sub.link = o.sub2 ;
189 | o.sub2.link = o.sub ;
190 |
191 | var c = tree.clone( o , true ) ;
192 |
193 | expect( c.loop ).to.be( c ) ;
194 | expect( c.sub ).to.be( c.subcopy ) ;
195 | expect( c.sub.loop ).to.be( c ) ;
196 | expect( c.subcopy.loop ).to.be( c ) ;
197 | expect( c.sub.link ).to.be( c.sub2 ) ;
198 | expect( c.sub2.link ).to.be( c.sub ) ;
199 | ```
200 |
201 | ... without *circular* on, the `clone()` method would run forever, creating a new object independant nested object each time
202 | it reaches the *loop* property.
203 | We can see that the *subcopy* property remains a reference of *sub* even in the clone, thanks to the *circular* option.
204 |
205 | However, if we are sure that there isn't multiple reference to the same object or circular references, we can gain a lot of
206 | performances by leaving that options off.
207 | It can save a lot of `.indexOf()` call on big data structure.
208 |
209 | This method does not uses `extend()` anymore like in version 0.3.x, it now uses its own optimized code.
210 | However it is equivalent to an `extend()` with those options turned on: *deep, own, nonEnum, descriptor & proto*.
211 | If *circular* is on, it has the same effect than the `extend()`'s *circular* option.
212 |
213 | **Also please note that design pattern emulating private members using a closure's scope cannot be truly cloned**
214 | (e.g. the *revealing pattern*).
215 | This is not possible to mutate a function's scope.
216 | So the clone's methods will continue to inherit the parent's scope of the original function.
217 |
218 |
219 |
220 |
221 | ## .diff( left , right , [options] )
222 |
223 | * left `Object` the left-hand side object structure
224 | * right `Object` the right-hand side object structure
225 | * options `Object` containing options, it supports:
226 | * path `string` the initial path, default: empty string
227 | * pathSeparator `string` the path separator, default: '.'
228 |
229 | This tool reports diff between a left-hand side and right-hand side object structure.
230 | It returns an object, each key is a path where a difference is reported, the value being an object containing (again) the path
231 | and a human-readable message.
232 |
233 | See this example:
234 | ```js
235 | var left = {
236 | a: 'a',
237 | b: 2,
238 | c: 'three',
239 | sub: {
240 | e: 5,
241 | f: 'six',
242 | }
243 | } ;
244 |
245 | var right = {
246 | b: 2,
247 | c: 3,
248 | d: 'dee',
249 | sub: {
250 | e: 5,
251 | f: 6,
252 | }
253 | } ;
254 |
255 | console.log( tree.diff( a , b ) ) ;
256 | ```
257 | It will output:
258 | ```js
259 | { '.a': { path: '.a', message: 'does not exist in right-hand side' },
260 | '.c': { path: '.c', message: 'different typeof: string - number' },
261 | '.sub.f': { path: '.sub.f', message: 'different typeof: string - number' },
262 | '.d': { path: '.d', message: 'does not exist in left-hand side' } }
263 | ```
264 |
265 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 | ## Reporting a Vulnerability
3 |
4 | If you think you have found a vulnerability, _please report responsibly_.
5 | Don't create GitHub issues for security issues.
6 | Instead, send an email to cedric dot ronvel at gmail dot com and I will look into it as soon as possible.
7 |
8 | **A note for bounty hunters:** I should mention that I *usually* prefer to fix security issues by myself,
9 | because it could involve rethinking API or fixing it / working around it in a way only an official maintainer can do it.
10 | I want to avoid people getting frustrated: **don't work on a fix before getting in touch with me**.
11 |
--------------------------------------------------------------------------------
/bench/path-vs-dotPath.js:
--------------------------------------------------------------------------------
1 |
2 | "use strict" ;
3 |
4 | /* global benchmark, competitor */
5 |
6 |
7 |
8 | var path = require( '../lib/path.js' ) ;
9 | var dotPath = require( '../lib/dotPath.js' ) ;
10 |
11 |
12 |
13 | benchmark( 'get a value through a path' , () => {
14 | var data = {
15 | propertyA: "value1" ,
16 | propertyB: "value2" ,
17 | object: {
18 | propertyC: "value3" ,
19 | propertyD: "value4" ,
20 | long: { nested: { path: { to: { a: { value: {} } } } } }
21 | } ,
22 | array: [ 'element1' , 'element2' , 'element3' , [ 'element4' , 'element5' , 'element6' , [ 'element7' , 'element8' , 'element9' ] ] ]
23 | } ;
24 |
25 | competitor( 'path.get()' , () => {
26 | var output ;
27 |
28 | output = path.get( data , 'propertyA' ) ;
29 | output = path.get( data , 'object' ) ;
30 | output = path.get( data , 'object.propertyC' ) ;
31 | output = path.get( data , 'object.long.nested.path.to.a.value' ) ;
32 | output = path.get( data , 'array' ) ;
33 | output = path.get( data , 'array.0' ) ;
34 | output = path.get( data , 'array.1' ) ;
35 | output = path.get( data , 'array.3' ) ;
36 | output = path.get( data , 'array.3.1' ) ;
37 | output = path.get( data , 'array.3.3.1' ) ;
38 | } ) ;
39 |
40 | competitor( 'path.get() with path-array' , () => {
41 | var output ;
42 |
43 | output = path.get( data , ['propertyA'] ) ;
44 | output = path.get( data , ['object'] ) ;
45 | output = path.get( data , ['object','propertyC'] ) ;
46 | output = path.get( data , ['object','long','nested','path','to','a','value'] ) ;
47 | output = path.get( data , ['array'] ) ;
48 | output = path.get( data , ['array',0] ) ;
49 | output = path.get( data , ['array',1] ) ;
50 | output = path.get( data , ['array',3] ) ;
51 | output = path.get( data , ['array',3,1] ) ;
52 | output = path.get( data , ['array',3,3,1] ) ;
53 | } ) ;
54 |
55 | competitor( 'dotPath.get()' , () => {
56 | var output ;
57 |
58 | output = dotPath.get( data , 'propertyA' ) ;
59 | output = dotPath.get( data , 'object' ) ;
60 | output = dotPath.get( data , 'object.propertyC' ) ;
61 | output = dotPath.get( data , 'object.long.nested.dotPath.to.a.value' ) ;
62 | output = dotPath.get( data , 'array' ) ;
63 | output = dotPath.get( data , 'array.0' ) ;
64 | output = dotPath.get( data , 'array.1' ) ;
65 | output = dotPath.get( data , 'array.3' ) ;
66 | output = dotPath.get( data , 'array.3.1' ) ;
67 | output = dotPath.get( data , 'array.3.3.1' ) ;
68 | } ) ;
69 |
70 | competitor( 'dotPath.get() with path-array' , () => {
71 | var output ;
72 |
73 | output = dotPath.get( data , ['propertyA'] ) ;
74 | output = dotPath.get( data , ['object'] ) ;
75 | output = dotPath.get( data , ['object','propertyC'] ) ;
76 | output = dotPath.get( data , ['object','long','nested','path','to','a','value'] ) ;
77 | output = dotPath.get( data , ['array'] ) ;
78 | output = dotPath.get( data , ['array',0] ) ;
79 | output = dotPath.get( data , ['array',1] ) ;
80 | output = dotPath.get( data , ['array',3] ) ;
81 | output = dotPath.get( data , ['array',3,1] ) ;
82 | output = dotPath.get( data , ['array',3,3,1] ) ;
83 | } ) ;
84 | } ) ;
85 |
86 |
87 |
88 | benchmark( 'get a value through a long path' , () => {
89 | var data = {
90 | propertyA: "value1" ,
91 | propertyB: "value2" ,
92 | object: {
93 | propertyC: "value3" ,
94 | propertyD: "value4" ,
95 | long: { nested: { path: { to: { a: { value: {} } } } } }
96 | } ,
97 | array: [ 'element1' , 'element2' , 'element3' , [ 'element4' , 'element5' , 'element6' , [ 'element7' , 'element8' , 'element9' , [[['element10']]] ] ] ]
98 | } ;
99 |
100 | competitor( 'path.get()' , () => {
101 | var output ;
102 | output = path.get( data , 'object.long.nested.path.to.a.value' ) ;
103 | output = path.get( data , 'array.3.3.3.0.0.0' ) ;
104 | } ) ;
105 |
106 | competitor( 'path.get() with path-array' , () => {
107 | var output ;
108 | output = path.get( data , ['object','long','nested','path','to','a','value'] ) ;
109 | output = path.get( data , ['array',3,3,3,0,0,0] ) ;
110 | } ) ;
111 |
112 | competitor( 'dotPath.get()' , () => {
113 | var output ;
114 | output = dotPath.get( data , 'object.long.nested.dotPath.to.a.value' ) ;
115 | output = dotPath.get( data , 'array.3.3.3.0.0.0' ) ;
116 | } ) ;
117 |
118 | competitor( 'dotPath.get() with path-array' , () => {
119 | var output ;
120 | output = dotPath.get( data , ['object','long','nested','dotPath','to','a','value'] ) ;
121 | output = dotPath.get( data , ['array',3,3,3,0,0,0] ) ;
122 | } ) ;
123 | } ) ;
124 |
125 |
--------------------------------------------------------------------------------
/browser/tree-kit.min.js:
--------------------------------------------------------------------------------
1 | (function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.treeKit=e()}})(function(){var e,t,r;return function(){function e(t,r,n){function o(i,u){if(!r[i]){if(!t[i]){var s="function"==typeof require&&require;if(!u&&s)return s(i,!0);if(f)return f(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var c=r[i]={exports:{}};t[i][0].call(c.exports,function(e){var r=t[i][1][e];return o(r||e)},c,c.exports,e,t,r,n)}return r[i].exports}for(var f="function"==typeof require&&require,i=0;i[e[0],t(e[1])]))};r.filter=function(e,t){if(!e||typeof e!=="object"){throw new Error("Expecting an object")}return Object.fromEntries(Object.entries(e).filter(e=>t(e[1])))}},{}],2:[function(e,t,r){"use strict";const n={};t.exports=n;n.extend=e("./extend.js");n.clone=e("./clone.js");n.path=e("./path.js");n.dotPath=e("./dotPath.js");n.wildDotPath=e("./wildDotPath.js");Object.assign(n,e("./arrayLike.js"))},{"./arrayLike.js":1,"./clone.js":3,"./dotPath.js":4,"./extend.js":5,"./path.js":6,"./wildDotPath.js":7}],3:[function(e,t,r){"use strict";function n(e,t){var r=Object.getPrototypeOf(e);if(n.opaque.has(r)){return n.opaque.get(r)(e)}var o,f,i,u,s,a,c=[{source:e,target:Array.isArray(e)?[]:Object.create(r)}],p=c[0].target,l=new Map;l.set(e,p);while(u=c.shift()){i=Object.getOwnPropertyNames(u.source);for(o=0;onew Date(e))},{}],4:[function(e,t,r){"use strict";const n={};t.exports=n;const o=[];const f="This would cause prototype pollution";function i(e){if(Array.isArray(e)){return e}if(!e){return o}if(typeof e==="string"){return e[e.length-1]==="."?e.slice(0,-1).split("."):e.split(".")}throw new TypeError("[tree.dotPath]: the path argument should be a string or an array")}n.toPathArray=i;function u(e,t,r=0){var n,o,i,u=e;for(n=0,o=t.length+r;nu(e,i(t)));n.set=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);u[o]=r;return r});n.define=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!(o in u)){u[o]=r}return u[o]});n.inc=((e,t)=>{if(!e||typeof e!=="object"){return}var r=i(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(f)}var o=s(e,r);if(typeof o[n]==="number"){o[n]++}else if(!o[n]||typeof o[n]!=="object"){o[n]=1}return o[n]});n.dec=((e,t)=>{if(!e||typeof e!=="object"){return}var r=i(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(f)}var o=s(e,r);if(typeof o[n]==="number"){o[n]--}else if(!o[n]||typeof o[n]!=="object"){o[n]=-1}return o[n]});n.concat=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=r}else if(Array.isArray(u[o])&&Array.isArray(r)){u[o]=u[o].concat(r)}return u[o]});n.insert=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=r}else if(Array.isArray(u[o])&&Array.isArray(r)){u[o]=r.concat(u[o])}return u[o]});n.delete=((e,t)=>{var r=i(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(f)}var o=u(e,r,-1);if(!o||typeof o!=="object"||!Object.hasOwn(o,n)){return false}delete o[n];return true});n.autoPush=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(u[o]===undefined){u[o]=r}else if(Array.isArray(u[o])){u[o].push(r)}else{u[o]=[u[o],r]}return u[o]});n.append=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=[r]}else if(Array.isArray(u[o])){u[o].push(r)}return u[o]});n.prepend=((e,t,r)=>{if(!e||typeof e!=="object"){return}var n=i(t),o=n[n.length-1];if(typeof o==="object"||o==="__proto__"){throw new Error(f)}var u=s(e,n);if(!u[o]){u[o]=[r]}else if(Array.isArray(u[o])){u[o].unshift(r)}return u[o]})},{}],5:[function(e,t,r){"use strict";function n(e,t,...r){var n,f,i=false,u=r.length;if(!u){return t}if(!e||typeof e!=="object"){e={}}var s={depth:0,prefix:""};if(e.deep){if(Array.isArray(e.deep)){e.deep=new Set(e.deep)}else if(!(e.deep instanceof Set)){e.deep=true}}if(e.immutables){if(Array.isArray(e.immutables)){e.immutables=new Set(e.immutables)}else if(!(e.immutables instanceof Set)){delete e.immutables}}if(!e.maxDepth&&e.deep&&!e.circular){e.maxDepth=100}if(e.deepFunc){e.deep=true}if(e.flat){e.deep=true;e.proto=false;e.inherit=false;e.unflat=false;if(typeof e.flat!=="string"){e.flat="."}}if(e.unflat){e.deep=false;e.proto=false;e.inherit=false;e.flat=false;if(typeof e.unflat!=="string"){e.unflat="."}}if(e.inherit){e.own=true;e.proto=false}else if(e.proto){e.own=true}if(!t||typeof t!=="object"&&typeof t!=="function"){i=true}if(!e.skipRoot&&(e.inherit||e.proto)){for(n=u-1;n>=0;n--){f=r[n];if(f&&(typeof f==="object"||typeof f==="function")){if(e.inherit){if(i){t=Object.create(f)}else{Object.setPrototypeOf(t,f)}}else if(e.proto){if(i){t=Object.create(Object.getPrototypeOf(f))}else{Object.setPrototypeOf(t,Object.getPrototypeOf(f))}}break}}}else if(i){t={}}s.references={sources:[],targets:[]};for(n=0;nt.maxDepth){throw new Error("[tree] extend(): max depth reached("+t.maxDepth+")")}if(t.circular){e.references.sources.push(n);e.references.targets.push(r)}if(t.unflat&&e.depth===0){for(u in n){e.unflatKeys=u.split(t.unflat);e.unflatIndex=0;e.unflatFullKey=u;f(e,t,r,n,e.unflatKeys[e.unflatIndex],o)}delete e.unflatKeys;delete e.unflatIndex;delete e.unflatFullKey}else if(t.own){if(t.nonEnum){i=Object.getOwnPropertyNames(n)}else{i=Object.keys(n)}for(u of i){f(e,t,r,n,u,o)}}else{for(u in n){f(e,t,r,n,u,o)}}}function f(e,t,r,n,i,u){if(i==="__proto__"){return}let s,a,c;if(e.unflatKeys){if(e.unflatIndex=0){return}o({depth:e.depth+1,prefix:e.prefix+i+t.flat,references:e.references},t,r,s,u)}else{if(y>=0){l=e.references.targets[y];if(t.descriptor){Object.defineProperty(r,p,{value:l,enumerable:a.enumerable,writable:a.writable,configurable:a.configurable})}else{r[p]=l}return}if(!d||!Object.hasOwn(r,p)){if(Array.isArray(s)){l=[]}else if(t.proto){l=Object.create(c)}else if(t.inherit){l=Object.create(s)}else{l={}}if(t.descriptor){Object.defineProperty(r,p,{value:l,enumerable:a.enumerable,writable:a.writable,configurable:a.configurable})}else{r[p]=l}}else if(t.proto&&Object.getPrototypeOf(l)!==c){Object.setPrototypeOf(l,c)}else if(t.inherit&&Object.getPrototypeOf(l)!==s){Object.setPrototypeOf(l,s)}if(t.circular){e.references.sources.push(s);e.references.targets.push(l)}if(e.unflatKeys&&e.unflatIndexu(e,i(t)));n.getPaths=((e,t)=>p(e,i(t),a));n.getPathValueMap=((e,t)=>p(e,i(t),c));function l(e,t,r,n,o=0){var i,u,s=e;for(i=t.length-1;o{e[t]=r;return 1};n.set=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),d,r)});const y=(e,t,r)=>{if(t in e){return 0}e[t]=r;return 1};n.define=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),y,r)});const b=(e,t)=>{if(typeof e[t]==="number"){e[t]++}else if(!e[t]||typeof e[t]!=="object"){e[t]=1}return 1};n.inc=((e,t)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),b)});const h=(e,t)=>{if(typeof e[t]==="number"){e[t]--}else if(!e[t]||typeof e[t]!=="object"){e[t]=-1}return 1};n.dec=((e,t)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),h)});const j=(e,t,r)=>{if(!e[t]){e[t]=r}else if(Array.isArray(e[t])&&Array.isArray(r)){e[t]=e[t].concat(r)}return 1};n.concat=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),j,r)});const w=(e,t,r)=>{if(!e[t]){e[t]=r}else if(Array.isArray(e[t])&&Array.isArray(r)){e[t]=r.concat(e[t])}return 1};n.insert=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),w,r)});const g=(e,t)=>{if(!Object.hasOwn(e,t)){return 0}delete e[t];return 1};n.delete=((e,t)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),g)});const _=(e,t,r)=>{if(e[t]===undefined){e[t]=r}else if(Array.isArray(e[t])){e[t].push(r)}else{e[t]=[e[t],r]}return 1};n.autoPush=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),_,r)});const A=(e,t,r)=>{if(!e[t]){e[t]=[r]}else if(Array.isArray(e[t])){e[t].push(r)}return 1};n.append=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),A,r)});const v=(e,t,r)=>{if(!e[t]){e[t]=[r]}else if(Array.isArray(e[t])){e[t].unshift(r)}return 1};n.prepend=((e,t,r)=>{if(!e||typeof e!=="object"){return 0}return l(e,i(t),v,r)})},{}]},{},[2])(2)});
--------------------------------------------------------------------------------
/documentation.md:
--------------------------------------------------------------------------------
1 |
2 | # Tree Kit
3 |
4 | This lib is a toolbox that provide functions to operate with nested `Object` structure.
5 | It features the best `.extend()` method, providing dozen of options that all others libs miss.
6 |
7 | * License: MIT
8 | * Current status: release candidate
9 | * Platform: Node.js only (browser support is planned)
10 |
11 | Some tutorials are available at [blog.soulserv.net/tag/tree-kit](http://blog.soulserv.net/tag/tree-kit/).
12 |
13 |
14 |
15 | # Install
16 |
17 | Use Node Package Manager:
18 |
19 | npm install tree-kit
20 |
21 |
22 |
23 | # Library references
24 |
25 | * [.extend()](#ref.extend): full-featured extend facility, copy, clone, extend
26 | * [.clone()](#ref.clone): clone any object
27 | * [.diff()](#ref.diff): report differences between two objects
28 |
29 |
30 |
31 | In all examples below, it is assumed that you have required the lib into the `tree` variable:
32 | ```js
33 | var tree = require( 'tree-kit' ) ;
34 | ```
35 |
36 |
37 |
38 |
39 | ## .extend( options , target , source1 , [source2] , [...] )
40 |
41 | * options `Object` extend options, it supports the properties:
42 | * own `boolean` only copy enumerable own properties from the sources
43 | * nonEnum `boolean` copy non-enumerable properties as well, works only with own:true
44 | * descriptor `boolean` preserve property's descriptor (i.e. writable, enumerable, configurable, get & set)
45 | * deep: `boolean` or `Array` or `Set`, if true perform a deep (recursive) extend, if it is an Array/Set of prototypes, only deep-copy
46 | objects of those prototypes (it is a replacement for deepFilter.whitelist which was removed in Tree Kit 0.6)
47 | * immutables: an Array/Set of immutable object's prototypes that are filtered out for deep-copy (it is a replacement
48 | for deepFilter.blacklist which was removed in Tree Kit 0.6)
49 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected
50 | (referenced), if false then nested object are blindly cloned
51 | * maxDepth `integer` used in conjunction with deep, when the max depth is reached an exception is raised, it defaults to 100
52 | when the 'circular' option is off, or defaults to null if 'circular' is on
53 | * move `boolean` move properties from the sources object to the target object (delete properties from the sources object)
54 | * preserve `boolean` existing properties in the target object will not be overwritten
55 | * nofunc `boolean` skip properties that are functions
56 | * deepFunc `boolean` in conjunction with 'deep', this will process sources functions like objects rather than
57 | copying/referencing them directly into the source (default behaviour), thus, the result will not be a function,
58 | it forces 'deep' options
59 | * proto `boolean` alter the target's prototype so that it matches the source's prototype.
60 | It forces option 'own'. Specifying multiple sources does not make sens here.
61 | * inherit `boolean` make the target inherit from the source (the target's prototype will be the source itself, not its prototype).
62 | It forces option 'own' and disable 'proto'. Specifying multiple sources does not make sens here.
63 | * skipRoot `boolean` prevent the prototype of the target **root** object from mutation.
64 | Only nested objects' prototype will be mutated.
65 | * flat `boolean|string` sources properties are copied in a way to produce a *flat* target, the target's key
66 | is the full path (separated by '.') of the source's key, also if a string is provided it will be used as
67 | the path separator
68 | * unflat `boolean|string` it is the opposite of 'flat': assuming that the sources are in the *flat* format,
69 | it expands all flat properties -- whose name are path with '.' as the separator -- deeply into the target,
70 | also if a string is provided it will be used as the path separator
71 | * target `Object` the target of the extend, properties will be copied to this object
72 | * source1 `Object` the source of the extend, properties will be copied from this object
73 | * ...
74 |
75 | This is a full-featured *extend* of an object with one or more source object.
76 |
77 | It is easily translated from jQuery-like *extend()*:
78 | * `extend( target , source )` translate into `tree.extend( null , target , source )`
79 | * `extend( true , target , source )` translate into `tree.extend( { deep: true } , target , source )`
80 |
81 | However, here we have full control over what will be extended and how.
82 |
83 | **All the options above are inactive by default**.
84 | You can pass null as argument #0 to get the default behaviour (= all options are inactive).
85 | So using the default behaviour, `tree.extend()` will copy all enumerable properties, and perform a shallow copy (a nested object
86 | is not cloned, it remains a reference of the original one).
87 |
88 | With the *deep* option, a deep copy is performed, so nested object are cloned too.
89 |
90 | The *own* option clone only owned properties from the sources, properties that are part of the source's prototype would not
91 | be copied/cloned.
92 |
93 | The *nonEnum* option will clone properties that are not enumerable.
94 |
95 | The *descriptor* option will preserve property's descriptor, e.g. if the source property is not writable and not enumerable,
96 | so will be the copied property.
97 |
98 | In case of a *getter* properties:
99 |
100 | * without the *descriptor* option, the getter function of the source object will be called, the return value will be put
101 | into the target property (so it lose its getter/setter behaviour)
102 | * with the *descriptor* option, the getter & setter function of the source object will be copied (but not called) into the target
103 | property: the getter/setter behaviour is preserved
104 |
105 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it.
106 | We can see this *circular* feature in action in [this example](#example.circular).
107 |
108 | Mixing *inherit* and *deep* provides a nice multi-level inheritance.
109 |
110 | With the *flat* option example:
111 | ```js
112 | var o = {
113 | one: 1,
114 | sub: {
115 | two: 2,
116 | three: 3
117 | }
118 | } ;
119 |
120 | var flatCopy = tree.extend( { flat: true } , {} , o ) ;
121 | ```
122 | ... it will produce:
123 | ```js
124 | {
125 | one: 1,
126 | "sub.two": 2,
127 | "sub.three": 3
128 | }
129 | ```
130 |
131 | By the way, the *unflat* option does the opposite, and thus can reverse this back to the original form.
132 |
133 | The *deepFilter* option is used when you do not want to clone some type of object.
134 | Let's say you want a deep copy except for `Buffer` objects, you simply want them to share the same reference:
135 | ```js
136 | var o = {
137 | one: '1' ,
138 | buf: new Buffer( "My buffer" ) ,
139 | subtree: {
140 | two: 2 ,
141 | three: 'THREE'
142 | }
143 | } ;
144 |
145 | // either
146 | var extended1 = tree.extend( { deep: true, deepFilter: { whitelist: [ Object.prototype ] } } , {} , o ) ;
147 | // or
148 | var extended2 = tree.extend( { deep: true, deepFilter: { blacklist: [ Buffer.prototype ] } } , {} , o ) ;
149 | ```
150 |
151 | Doing this, we have `o.buf === extended1.buf === extended2.buf`, and `o.subtree !== extended1.subtree !== extended2.subtree`.
152 |
153 |
154 |
155 |
156 | ## .clone( original , [circular] )
157 |
158 | * original `Object` the source object to clone
159 | * circular `boolean` (default to false) if true then circular references are checked and each identical objects are reconnected
160 | (referenced), if false then nested object are blindly cloned
161 |
162 | It returns a clone of the *original* object, providing the best object-cloning facility that this lib can offer.
163 |
164 | The clone produced are perfect independant copy **in 99% of use case**, but there is one big limitation:
165 | method that access variables in the parent's scope.
166 |
167 | The clone will share those variables with the *original* object, so they are not totally independant entity.
168 | Design pattern using closure to emulate *private member* (e.g. the revealing pattern) can cause trouble.
169 |
170 | If *circular* is on, the lib will detect when the source's data structure reuses the same object multiple time and will preserve it.
171 |
172 |
173 | Here is an example of this *circular* feature:
174 | ```js
175 | var o = {
176 | a: 'a',
177 | sub: {
178 | b: 'b'
179 | },
180 | sub2: {
181 | c: 'c'
182 | }
183 | } ;
184 |
185 | o.loop = o ;
186 | o.sub.loop = o ;
187 | o.subcopy = o.sub ;
188 | o.sub.link = o.sub2 ;
189 | o.sub2.link = o.sub ;
190 |
191 | var c = tree.clone( o , true ) ;
192 |
193 | expect( c.loop ).to.be( c ) ;
194 | expect( c.sub ).to.be( c.subcopy ) ;
195 | expect( c.sub.loop ).to.be( c ) ;
196 | expect( c.subcopy.loop ).to.be( c ) ;
197 | expect( c.sub.link ).to.be( c.sub2 ) ;
198 | expect( c.sub2.link ).to.be( c.sub ) ;
199 | ```
200 |
201 | ... without *circular* on, the `clone()` method would run forever, creating a new object independant nested object each time
202 | it reaches the *loop* property.
203 | We can see that the *subcopy* property remains a reference of *sub* even in the clone, thanks to the *circular* option.
204 |
205 | However, if we are sure that there isn't multiple reference to the same object or circular references, we can gain a lot of
206 | performances by leaving that options off.
207 | It can save a lot of `.indexOf()` call on big data structure.
208 |
209 | This method does not uses `extend()` anymore like in version 0.3.x, it now uses its own optimized code.
210 | However it is equivalent to an `extend()` with those options turned on: *deep, own, nonEnum, descriptor & proto*.
211 | If *circular* is on, it has the same effect than the `extend()`'s *circular* option.
212 |
213 | **Also please note that design pattern emulating private members using a closure's scope cannot be truly cloned**
214 | (e.g. the *revealing pattern*).
215 | This is not possible to mutate a function's scope.
216 | So the clone's methods will continue to inherit the parent's scope of the original function.
217 |
218 |
219 |
220 |
221 | ## .diff( left , right , [options] )
222 |
223 | * left `Object` the left-hand side object structure
224 | * right `Object` the right-hand side object structure
225 | * options `Object` containing options, it supports:
226 | * path `string` the initial path, default: empty string
227 | * pathSeparator `string` the path separator, default: '.'
228 |
229 | This tool reports diff between a left-hand side and right-hand side object structure.
230 | It returns an object, each key is a path where a difference is reported, the value being an object containing (again) the path
231 | and a human-readable message.
232 |
233 | See this example:
234 | ```js
235 | var left = {
236 | a: 'a',
237 | b: 2,
238 | c: 'three',
239 | sub: {
240 | e: 5,
241 | f: 'six',
242 | }
243 | } ;
244 |
245 | var right = {
246 | b: 2,
247 | c: 3,
248 | d: 'dee',
249 | sub: {
250 | e: 5,
251 | f: 6,
252 | }
253 | } ;
254 |
255 | console.log( tree.diff( a , b ) ) ;
256 | ```
257 | It will output:
258 | ```js
259 | { '.a': { path: '.a', message: 'does not exist in right-hand side' },
260 | '.c': { path: '.c', message: 'different typeof: string - number' },
261 | '.sub.f': { path: '.sub.f', message: 'different typeof: string - number' },
262 | '.d': { path: '.d', message: 'does not exist in left-hand side' } }
263 | ```
264 |
265 |
--------------------------------------------------------------------------------
/lib/arrayLike.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | // Mimic Array's prototype methods, like Array#map(), Array#filter(), and so on...
32 |
33 | exports.map = function( object , fn ) {
34 | if ( ! object || typeof object !== 'object' ) { throw new Error( "Expecting an object" ) ; }
35 | return Object.fromEntries( Object.entries( object ).map( entry => [ entry[ 0 ] , fn( entry[ 1 ] ) ] ) ) ;
36 | } ;
37 |
38 | exports.filter = function( object , fn ) {
39 | if ( ! object || typeof object !== 'object' ) { throw new Error( "Expecting an object" ) ; }
40 | return Object.fromEntries( Object.entries( object ).filter( entry => fn( entry[ 1 ] ) ) ) ;
41 | } ;
42 |
43 |
--------------------------------------------------------------------------------
/lib/browser.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | // Browser only get the essential of tree-kit, not the unfinished parts of it
32 |
33 | const tree = {} ;
34 | module.exports = tree ;
35 |
36 |
37 |
38 | tree.extend = require( './extend.js' ) ;
39 | tree.clone = require( './clone.js' ) ;
40 | tree.path = require( './path.js' ) ;
41 | tree.dotPath = require( './dotPath.js' ) ;
42 | tree.wildDotPath = require( './wildDotPath.js' ) ;
43 |
44 | Object.assign( tree , require( './arrayLike.js' ) ) ;
45 |
46 |
--------------------------------------------------------------------------------
/lib/clone.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | /*
32 | Stand-alone fork of extend.js, without options.
33 | */
34 |
35 | function clone( originalObject , circular ) {
36 | // First create an empty object with
37 | // same prototype of our original source
38 |
39 | var originalProto = Object.getPrototypeOf( originalObject ) ;
40 |
41 | // Opaque objects, like Date
42 | if ( clone.opaque.has( originalProto ) ) { return clone.opaque.get( originalProto )( originalObject ) ; }
43 |
44 | var propertyIndex , descriptor , keys , current , nextSource , proto ,
45 | copies = [ {
46 | source: originalObject ,
47 | target: Array.isArray( originalObject ) ? [] : Object.create( originalProto )
48 | } ] ,
49 | cloneObject = copies[ 0 ].target ,
50 | refMap = new Map() ;
51 |
52 | refMap.set( originalObject , cloneObject ) ;
53 |
54 |
55 | // First in, first out
56 | while ( ( current = copies.shift() ) ) {
57 | keys = Object.getOwnPropertyNames( current.source ) ;
58 |
59 | for ( propertyIndex = 0 ; propertyIndex < keys.length ; propertyIndex ++ ) {
60 | // Save the source's descriptor
61 | descriptor = Object.getOwnPropertyDescriptor( current.source , keys[ propertyIndex ] ) ;
62 |
63 |
64 | if ( ! descriptor.value || typeof descriptor.value !== 'object' ) {
65 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ;
66 | continue ;
67 | }
68 |
69 | nextSource = descriptor.value ;
70 |
71 | if ( circular ) {
72 | if ( refMap.has( nextSource ) ) {
73 | // The source is already referenced, just assign reference
74 | descriptor.value = refMap.get( nextSource ) ;
75 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ;
76 | continue ;
77 | }
78 | }
79 |
80 | proto = Object.getPrototypeOf( descriptor.value ) ;
81 |
82 | // Opaque objects, like Date, not recursivity for them
83 | if ( clone.opaque.has( proto ) ) {
84 | descriptor.value = clone.opaque.get( proto )( descriptor.value ) ;
85 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ;
86 | continue ;
87 | }
88 |
89 | descriptor.value = Array.isArray( nextSource ) ? [] : Object.create( proto ) ;
90 |
91 | if ( circular ) { refMap.set( nextSource , descriptor.value ) ; }
92 |
93 | Object.defineProperty( current.target , keys[ propertyIndex ] , descriptor ) ;
94 | copies.push( { source: nextSource , target: descriptor.value } ) ;
95 | }
96 | }
97 |
98 | return cloneObject ;
99 | }
100 |
101 | module.exports = clone ;
102 |
103 |
104 |
105 | clone.opaque = new Map() ;
106 | clone.opaque.set( Date.prototype , src => new Date( src ) ) ;
107 |
108 |
--------------------------------------------------------------------------------
/lib/diff.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | /*
32 | == Diff function ==
33 | */
34 |
35 | function diff( left , right , options ) {
36 | var i , key , keyPath ,
37 | leftKeys , rightKeys , leftTypeof , rightTypeof ,
38 | depth , diffObject , length , arrayMode ;
39 |
40 | leftTypeof = typeof left ;
41 | rightTypeof = typeof right ;
42 |
43 | if (
44 | ! left || ( leftTypeof !== 'object' && leftTypeof !== 'function' ) ||
45 | ! right || ( rightTypeof !== 'object' && rightTypeof !== 'function' )
46 | ) {
47 | throw new Error( '[tree] diff() needs objects as argument #0 and #1' ) ;
48 | }
49 |
50 | if ( ! options || typeof options !== 'object' ) { options = {} ; }
51 |
52 | depth = options.depth || 0 ;
53 |
54 | // Things applied only for the root, not for recursive call
55 | if ( ! depth ) {
56 | options.diffObject = {} ;
57 | if ( ! options.path ) { options.path = '' ; }
58 | if ( ! options.pathSeparator ) { options.pathSeparator = '.' ; }
59 | }
60 |
61 | diffObject = options.diffObject ;
62 |
63 |
64 | // Left part
65 | if ( Array.isArray( left ) ) {
66 | arrayMode = true ;
67 | length = left.length ;
68 | }
69 | else {
70 | arrayMode = false ;
71 | leftKeys = Object.keys( left ) ;
72 | length = leftKeys.length ;
73 | }
74 |
75 | for ( i = 0 ; i < length ; i ++ ) {
76 | key = arrayMode ? i : leftKeys[ i ] ;
77 | keyPath = options.path + options.pathSeparator + key ;
78 | //console.log( 'L keyPath:' , keyPath ) ;
79 |
80 | if ( ! Object.hasOwn( right , key ) ) {
81 | diffObject[ keyPath ] = { path: keyPath , message: 'does not exist in right-hand side' } ;
82 | continue ;
83 | }
84 |
85 | leftTypeof = typeof left[ key ] ;
86 | rightTypeof = typeof right[ key ] ;
87 |
88 | if ( leftTypeof !== rightTypeof ) {
89 | diffObject[ keyPath ] = { path: keyPath , message: 'different typeof: ' + leftTypeof + ' - ' + rightTypeof } ;
90 | continue ;
91 | }
92 |
93 | if ( leftTypeof === 'object' || leftTypeof === 'function' ) {
94 | // Cleanup the 'null is an object' mess
95 | if ( ! left[ key ] ) {
96 | if ( right[ key ] ) { diffObject[ keyPath ] = { path: keyPath , message: 'different type: null - Object' } ; }
97 | continue ;
98 | }
99 |
100 | if ( ! right[ key ] ) {
101 | diffObject[ keyPath ] = { path: keyPath , message: 'different type: Object - null' } ;
102 | continue ;
103 | }
104 |
105 | if ( Array.isArray( left[ key ] ) && ! Array.isArray( right[ key ] ) ) {
106 | diffObject[ keyPath ] = { path: keyPath , message: 'different type: Array - Object' } ;
107 | continue ;
108 | }
109 |
110 | if ( ! Array.isArray( left[ key ] ) && Array.isArray( right[ key ] ) ) {
111 | diffObject[ keyPath ] = { path: keyPath , message: 'different type: Object - Array' } ;
112 | continue ;
113 | }
114 |
115 | diff( left[ key ] , right[ key ] , {
116 | path: keyPath , pathSeparator: options.pathSeparator , depth: depth + 1 , diffObject: diffObject
117 | } ) ;
118 | continue ;
119 | }
120 |
121 | if ( left[ key ] !== right[ key ] ) {
122 | diffObject[ keyPath ] = { path: keyPath , message: 'different value: ' + left[ key ] + ' - ' + right[ key ] } ;
123 | continue ;
124 | }
125 | }
126 |
127 |
128 | // Right part
129 | if ( Array.isArray( right ) ) {
130 | arrayMode = true ;
131 | length = right.length ;
132 | }
133 | else {
134 | arrayMode = false ;
135 | rightKeys = Object.keys( right ) ;
136 | length = rightKeys.length ;
137 | }
138 |
139 | for ( i = 0 ; i < length ; i ++ ) {
140 | key = arrayMode ? i : rightKeys[ i ] ;
141 | keyPath = options.path + options.pathSeparator + key ;
142 | //console.log( 'R keyPath:' , keyPath ) ;
143 |
144 | if ( ! Object.hasOwn( left , key ) ) {
145 | diffObject[ keyPath ] = { path: keyPath , message: 'does not exist in left-hand side' } ;
146 | continue ;
147 | }
148 | }
149 |
150 | return Object.keys( diffObject ).length ? diffObject : null ;
151 | }
152 |
153 | exports.diff = diff ;
154 |
155 |
--------------------------------------------------------------------------------
/lib/dotPath.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | const dotPath = {} ;
32 | module.exports = dotPath ;
33 |
34 |
35 |
36 | const EMPTY_PATH = [] ;
37 | const PROTO_POLLUTION_MESSAGE = 'This would cause prototype pollution' ;
38 |
39 |
40 |
41 | function toPathArray( path ) {
42 | if ( Array.isArray( path ) ) {
43 | /*
44 | let i , iMax = path.length ;
45 | for ( i = 0 ; i < iMax ; i ++ ) {
46 | if ( typeof path[ i ] !== 'string' || typeof path[ i ] !== 'number' ) { path[ i ] = '' + path[ i ] ; }
47 | }
48 | //*/
49 | return path ;
50 | }
51 |
52 | if ( ! path ) { return EMPTY_PATH ; }
53 | if ( typeof path === 'string' ) {
54 | return path[ path.length - 1 ] === '.' ? path.slice( 0 , - 1 ).split( '.' ) : path.split( '.' ) ;
55 | }
56 |
57 | throw new TypeError( '[tree.dotPath]: the path argument should be a string or an array' ) ;
58 | }
59 |
60 | // Expose toPathArray()
61 | dotPath.toPathArray = toPathArray ;
62 |
63 |
64 |
65 | // Walk the tree using the path array.
66 | function walk( object , pathArray , maxOffset = 0 ) {
67 | var index , indexMax , key ,
68 | pointer = object ;
69 |
70 | for ( index = 0 , indexMax = pathArray.length + maxOffset ; index < indexMax ; index ++ ) {
71 | key = pathArray[ index ] ;
72 |
73 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
74 | if ( ! pointer || typeof pointer !== 'object' ) { return undefined ; }
75 |
76 | pointer = pointer[ key ] ;
77 | }
78 |
79 | return pointer ;
80 | }
81 |
82 |
83 |
84 | // Walk the tree, create missing element: pave the path up to before the last part of the path.
85 | // Return that before-the-last element.
86 | // Object MUST be an object! no check are performed for the first step!
87 | function pave( object , pathArray ) {
88 | var index , indexMax , key ,
89 | pointer = object ;
90 |
91 | for ( index = 0 , indexMax = pathArray.length - 1 ; index < indexMax ; index ++ ) {
92 | key = pathArray[ index ] ;
93 |
94 | if ( typeof key === 'object' || key === '__proto__' || typeof pointer[ key ] === 'function' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
95 | if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = {} ; }
96 |
97 | pointer = pointer[ key ] ;
98 | }
99 |
100 | return pointer ;
101 | }
102 |
103 |
104 |
105 | dotPath.get = ( object , path ) => walk( object , toPathArray( path ) ) ;
106 |
107 |
108 |
109 | dotPath.set = ( object , path , value ) => {
110 | if ( ! object || typeof object !== 'object' ) { return ; }
111 |
112 | var pathArray = toPathArray( path ) ,
113 | key = pathArray[ pathArray.length - 1 ] ;
114 |
115 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
116 |
117 | var pointer = pave( object , pathArray ) ;
118 |
119 | pointer[ key ] = value ;
120 |
121 | return value ;
122 | } ;
123 |
124 |
125 |
126 | dotPath.define = ( object , path , value ) => {
127 | if ( ! object || typeof object !== 'object' ) { return ; }
128 |
129 | var pathArray = toPathArray( path ) ,
130 | key = pathArray[ pathArray.length - 1 ] ;
131 |
132 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
133 |
134 | var pointer = pave( object , pathArray ) ;
135 |
136 | if ( ! ( key in pointer ) ) { pointer[ key ] = value ; }
137 |
138 | return pointer[ key ] ;
139 | } ;
140 |
141 |
142 |
143 | dotPath.inc = ( object , path ) => {
144 | if ( ! object || typeof object !== 'object' ) { return ; }
145 |
146 | var pathArray = toPathArray( path ) ,
147 | key = pathArray[ pathArray.length - 1 ] ;
148 |
149 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
150 |
151 | var pointer = pave( object , pathArray ) ;
152 |
153 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] ++ ; }
154 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = 1 ; }
155 |
156 | return pointer[ key ] ;
157 | } ;
158 |
159 |
160 |
161 | dotPath.dec = ( object , path ) => {
162 | if ( ! object || typeof object !== 'object' ) { return ; }
163 |
164 | var pathArray = toPathArray( path ) ,
165 | key = pathArray[ pathArray.length - 1 ] ;
166 |
167 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
168 |
169 | var pointer = pave( object , pathArray ) ;
170 |
171 | if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; }
172 | else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; }
173 |
174 | return pointer[ key ] ;
175 | } ;
176 |
177 |
178 |
179 | dotPath.concat = ( object , path , value ) => {
180 | if ( ! object || typeof object !== 'object' ) { return ; }
181 |
182 | var pathArray = toPathArray( path ) ,
183 | key = pathArray[ pathArray.length - 1 ] ;
184 |
185 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
186 |
187 | var pointer = pave( object , pathArray ) ;
188 |
189 | if ( ! pointer[ key ] ) { pointer[ key ] = value ; }
190 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) {
191 | pointer[ key ] = pointer[ key ].concat( value ) ;
192 | }
193 | //else ? do nothing???
194 |
195 | return pointer[ key ] ;
196 | } ;
197 |
198 |
199 |
200 | dotPath.insert = ( object , path , value ) => {
201 | if ( ! object || typeof object !== 'object' ) { return ; }
202 |
203 | var pathArray = toPathArray( path ) ,
204 | key = pathArray[ pathArray.length - 1 ] ;
205 |
206 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
207 |
208 | var pointer = pave( object , pathArray ) ;
209 |
210 | if ( ! pointer[ key ] ) { pointer[ key ] = value ; }
211 | else if ( Array.isArray( pointer[ key ] ) && Array.isArray( value ) ) {
212 | pointer[ key ] = value.concat( pointer[ key ] ) ;
213 | }
214 | //else ? do nothing???
215 |
216 | return pointer[ key ] ;
217 | } ;
218 |
219 |
220 |
221 | dotPath.delete = ( object , path ) => {
222 | var pathArray = toPathArray( path ) ,
223 | key = pathArray[ pathArray.length - 1 ] ;
224 |
225 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
226 |
227 | var pointer = walk( object , pathArray , - 1 ) ;
228 |
229 | if ( ! pointer || typeof pointer !== 'object' || ! Object.hasOwn( pointer , key ) ) { return false ; }
230 |
231 | delete pointer[ key ] ;
232 |
233 | return true ;
234 | } ;
235 |
236 |
237 |
238 | dotPath.autoPush = ( object , path , value ) => {
239 | if ( ! object || typeof object !== 'object' ) { return ; }
240 |
241 | var pathArray = toPathArray( path ) ,
242 | key = pathArray[ pathArray.length - 1 ] ;
243 |
244 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
245 |
246 | var pointer = pave( object , pathArray ) ;
247 |
248 | if ( pointer[ key ] === undefined ) { pointer[ key ] = value ; }
249 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; }
250 | else { pointer[ key ] = [ pointer[ key ] , value ] ; }
251 |
252 | return pointer[ key ] ;
253 | } ;
254 |
255 |
256 |
257 | dotPath.append = ( object , path , value ) => {
258 | if ( ! object || typeof object !== 'object' ) { return ; }
259 |
260 | var pathArray = toPathArray( path ) ,
261 | key = pathArray[ pathArray.length - 1 ] ;
262 |
263 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
264 |
265 | var pointer = pave( object , pathArray ) ;
266 |
267 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; }
268 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].push( value ) ; }
269 | //else ? do nothing???
270 |
271 | return pointer[ key ] ;
272 | } ;
273 |
274 |
275 |
276 | dotPath.prepend = ( object , path , value ) => {
277 | if ( ! object || typeof object !== 'object' ) { return ; }
278 |
279 | var pathArray = toPathArray( path ) ,
280 | key = pathArray[ pathArray.length - 1 ] ;
281 |
282 | if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; }
283 |
284 | var pointer = pave( object , pathArray ) ;
285 |
286 | if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; }
287 | else if ( Array.isArray( pointer[ key ] ) ) { pointer[ key ].unshift( value ) ; }
288 | //else ? do nothing???
289 |
290 | return pointer[ key ] ;
291 | } ;
292 |
293 |
--------------------------------------------------------------------------------
/lib/extend.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | /*
32 | == Extend function ==
33 | */
34 |
35 | /*
36 | options:
37 | * own: only copy own properties that are enumerable
38 | * nonEnum: copy non-enumerable properties as well, works only with own:true
39 | * descriptor: preserve property's descriptor
40 | * deep: boolean/Array/Set, if true perform a deep (recursive) extend, if it is an Array/Set of prototypes, only deep-copy
41 | objects of those prototypes
42 | (it is a replacement for deepFilter.whitelist which was removed in Tree Kit 0.6).
43 | * immutables: an Array/Set of immutable object's prototypes that are filtered out for deep-copy
44 | (it is a replacement for deepFilter.blacklist which was removed in Tree Kit 0.6).
45 | * maxDepth: used in conjunction with deep, when max depth is reached an exception is raised, default to 100 when
46 | the 'circular' option is off, or default to null if 'circular' is on
47 | * circular: boolean, circular references reconnection
48 | * move: boolean, move properties to target (delete properties from the sources)
49 | * preserve: boolean, existing properties in the target object are not overwritten
50 | * mask: boolean or number, reverse of 'preserve', only update existing properties in the target, do not create new keys,
51 | if its a number, the mask effect is only effective for the Nth element.
52 | E.g: .extend( {mask:2} , {} , object1 , object2 )
53 | So object1 extends the empty object like, but object2 do not create new keys not present in object1.
54 | With mask:true or mask:1, the mask behavior would apply at step 1 too, when object1 would try to extend the empty object,
55 | and since an empty object has no key, nothing would change, and the whole extend would return an empty object.
56 | * nofunc: skip functions
57 | * deepFunc: in conjunction with 'deep', this will process sources functions like objects rather than
58 | copying/referencing them directly into the source, thus, the result will not be a function, it forces 'deep'
59 | * proto: try to clone objects with the right prototype, using Object.create() or mutating it with Object.setPrototypeOf(),
60 | it forces option 'own'.
61 | * inherit: rather than mutating target prototype for source prototype like the 'proto' option does, here it is
62 | the source itself that IS the prototype for the target. Force option 'own' and disable 'proto'.
63 | * skipRoot: the prototype of the target root object is NOT mutated only if this option is set.
64 | * flat: extend into the target top-level only, compose name with the path of the source, force 'deep',
65 | disable 'unflat', 'proto', 'inherit'
66 | * unflat: assume sources are in the 'flat' format, expand all properties deeply into the target, disable 'flat'
67 | */
68 | function extend( options , target , ... sources ) {
69 | var i , source , newTarget = false , length = sources.length ;
70 |
71 | if ( ! length ) { return target ; }
72 |
73 | if ( ! options || typeof options !== 'object' ) { options = {} ; }
74 |
75 | var runtime = { depth: 0 , prefix: '' } ;
76 |
77 | if ( options.deep ) {
78 | if ( Array.isArray( options.deep ) ) { options.deep = new Set( options.deep ) ; }
79 | else if ( ! ( options.deep instanceof Set ) ) { options.deep = true ; }
80 | }
81 |
82 | if ( options.immutables ) {
83 | if ( Array.isArray( options.immutables ) ) { options.immutables = new Set( options.immutables ) ; }
84 | else if ( ! ( options.immutables instanceof Set ) ) { delete options.immutables ; }
85 | }
86 |
87 | if ( ! options.maxDepth && options.deep && ! options.circular ) { options.maxDepth = 100 ; }
88 |
89 | if ( options.deepFunc ) { options.deep = true ; }
90 |
91 | // 'flat' option force 'deep'
92 | if ( options.flat ) {
93 | options.deep = true ;
94 | options.proto = false ;
95 | options.inherit = false ;
96 | options.unflat = false ;
97 | if ( typeof options.flat !== 'string' ) { options.flat = '.' ; }
98 | }
99 |
100 | if ( options.unflat ) {
101 | options.deep = false ;
102 | options.proto = false ;
103 | options.inherit = false ;
104 | options.flat = false ;
105 | if ( typeof options.unflat !== 'string' ) { options.unflat = '.' ; }
106 | }
107 |
108 | // If the prototype is applied, only owned properties should be copied
109 | if ( options.inherit ) { options.own = true ; options.proto = false ; }
110 | else if ( options.proto ) { options.own = true ; }
111 |
112 | if ( ! target || ( typeof target !== 'object' && typeof target !== 'function' ) ) {
113 | newTarget = true ;
114 | }
115 |
116 | if ( ! options.skipRoot && ( options.inherit || options.proto ) ) {
117 | for ( i = length - 1 ; i >= 0 ; i -- ) {
118 | source = sources[ i ] ;
119 | if ( source && ( typeof source === 'object' || typeof source === 'function' ) ) {
120 | if ( options.inherit ) {
121 | if ( newTarget ) { target = Object.create( source ) ; }
122 | else { Object.setPrototypeOf( target , source ) ; }
123 | }
124 | else if ( options.proto ) {
125 | if ( newTarget ) { target = Object.create( Object.getPrototypeOf( source ) ) ; }
126 | else { Object.setPrototypeOf( target , Object.getPrototypeOf( source ) ) ; }
127 | }
128 |
129 | break ;
130 | }
131 | }
132 | }
133 | else if ( newTarget ) {
134 | target = {} ;
135 | }
136 |
137 | runtime.references = { sources: [] , targets: [] } ;
138 |
139 | for ( i = 0 ; i < length ; i ++ ) {
140 | source = sources[ i ] ;
141 | if ( ! source || ( typeof source !== 'object' && typeof source !== 'function' ) ) { continue ; }
142 | extendOne( runtime , options , target , source , options.mask <= i + 1 ) ;
143 | }
144 |
145 | return target ;
146 | }
147 |
148 | module.exports = extend ;
149 |
150 |
151 |
152 | function extendOne( runtime , options , target , source , mask ) {
153 | var sourceKeys , sourceKey ;
154 |
155 | // Max depth check
156 | if ( options.maxDepth && runtime.depth > options.maxDepth ) {
157 | throw new Error( '[tree] extend(): max depth reached(' + options.maxDepth + ')' ) ;
158 | }
159 |
160 |
161 | if ( options.circular ) {
162 | runtime.references.sources.push( source ) ;
163 | runtime.references.targets.push( target ) ;
164 | }
165 |
166 | // 'unflat' mode computing
167 | if ( options.unflat && runtime.depth === 0 ) {
168 | for ( sourceKey in source ) {
169 | runtime.unflatKeys = sourceKey.split( options.unflat ) ;
170 | runtime.unflatIndex = 0 ;
171 | runtime.unflatFullKey = sourceKey ;
172 | extendOneKV( runtime , options , target , source , runtime.unflatKeys[ runtime.unflatIndex ] , mask ) ;
173 | }
174 |
175 | delete runtime.unflatKeys ;
176 | delete runtime.unflatIndex ;
177 | delete runtime.unflatFullKey ;
178 | }
179 | else if ( options.own ) {
180 | if ( options.nonEnum ) { sourceKeys = Object.getOwnPropertyNames( source ) ; }
181 | else { sourceKeys = Object.keys( source ) ; }
182 |
183 | for ( sourceKey of sourceKeys ) {
184 | extendOneKV( runtime , options , target , source , sourceKey , mask ) ;
185 | }
186 | }
187 | else {
188 | for ( sourceKey in source ) {
189 | extendOneKV( runtime , options , target , source , sourceKey , mask ) ;
190 | }
191 | }
192 | }
193 |
194 |
195 |
196 | function extendOneKV( runtime , options , target , source , sourceKey , mask ) {
197 | // OMG, this DEPRECATED __proto__ shit is still alive and can be used to hack anything ><
198 | if ( sourceKey === '__proto__' ) { return ; }
199 |
200 | let sourceValue , sourceDescriptor , sourceValueProto ;
201 |
202 | if ( runtime.unflatKeys ) {
203 | if ( runtime.unflatIndex < runtime.unflatKeys.length - 1 ) {
204 | sourceValue = {} ;
205 | }
206 | else {
207 | sourceValue = source[ runtime.unflatFullKey ] ;
208 | }
209 | }
210 | else if ( options.descriptor ) {
211 | // If descriptor is on, get it now
212 | sourceDescriptor = Object.getOwnPropertyDescriptor( source , sourceKey ) ;
213 | sourceValue = sourceDescriptor.value ;
214 | }
215 | else {
216 | // We have to trigger an eventual getter only once
217 | sourceValue = source[ sourceKey ] ;
218 | }
219 |
220 | let targetKey = runtime.prefix + sourceKey ;
221 |
222 | // Do not copy if property is a function and we don't want them
223 | if ( options.nofunc && typeof sourceValue === 'function' ) { return ; }
224 |
225 | // Again, trigger an eventual getter only once
226 | let targetValue = target[ targetKey ] ;
227 | let targetValueIsObject = targetValue && ( typeof targetValue === 'object' || typeof targetValue === 'function' ) ;
228 | let sourceValueIsObject = sourceValue && ( typeof sourceValue === 'object' || typeof sourceValue === 'function' ) ;
229 |
230 | if (
231 | ( options.deep || runtime.unflatKeys )
232 | && sourceValue
233 | && ( typeof sourceValue === 'object' || ( options.deepFunc && typeof sourceValue === 'function' ) )
234 | && ( ! options.descriptor || ! sourceDescriptor.get )
235 | // not a condition we just cache sourceValueProto now... ok it's trashy ><
236 | && ( ( sourceValueProto = Object.getPrototypeOf( sourceValue ) ) || true )
237 | && ( ! ( options.deep instanceof Set ) || options.deep.has( sourceValueProto ) )
238 | && ( ! options.immutables || ! options.immutables.has( sourceValueProto ) )
239 | && ( ! options.preserve || targetValueIsObject )
240 | && ( ! mask || targetValueIsObject )
241 | ) {
242 | let indexOfSource = options.circular ? runtime.references.sources.indexOf( sourceValue ) : - 1 ;
243 |
244 | if ( options.flat ) {
245 | // No circular references reconnection when in 'flat' mode
246 | if ( indexOfSource >= 0 ) { return ; }
247 |
248 | extendOne(
249 | {
250 | depth: runtime.depth + 1 ,
251 | prefix: runtime.prefix + sourceKey + options.flat ,
252 | references: runtime.references
253 | } ,
254 | options , target , sourceValue , mask
255 | ) ;
256 | }
257 | else {
258 | if ( indexOfSource >= 0 ) {
259 | // Circular references reconnection...
260 | targetValue = runtime.references.targets[ indexOfSource ] ;
261 |
262 | if ( options.descriptor ) {
263 | Object.defineProperty( target , targetKey , {
264 | value: targetValue ,
265 | enumerable: sourceDescriptor.enumerable ,
266 | writable: sourceDescriptor.writable ,
267 | configurable: sourceDescriptor.configurable
268 | } ) ;
269 | }
270 | else {
271 | target[ targetKey ] = targetValue ;
272 | }
273 |
274 | return ;
275 | }
276 |
277 | if ( ! targetValueIsObject || ! Object.hasOwn( target , targetKey ) ) {
278 | if ( Array.isArray( sourceValue ) ) { targetValue = [] ; }
279 | else if ( options.proto ) { targetValue = Object.create( sourceValueProto ) ; }
280 | else if ( options.inherit ) { targetValue = Object.create( sourceValue ) ; }
281 | else { targetValue = {} ; }
282 |
283 | if ( options.descriptor ) {
284 | Object.defineProperty( target , targetKey , {
285 | value: targetValue ,
286 | enumerable: sourceDescriptor.enumerable ,
287 | writable: sourceDescriptor.writable ,
288 | configurable: sourceDescriptor.configurable
289 | } ) ;
290 | }
291 | else {
292 | target[ targetKey ] = targetValue ;
293 | }
294 | }
295 | else if ( options.proto && Object.getPrototypeOf( targetValue ) !== sourceValueProto ) {
296 | Object.setPrototypeOf( targetValue , sourceValueProto ) ;
297 | }
298 | else if ( options.inherit && Object.getPrototypeOf( targetValue ) !== sourceValue ) {
299 | Object.setPrototypeOf( targetValue , sourceValue ) ;
300 | }
301 |
302 | if ( options.circular ) {
303 | runtime.references.sources.push( sourceValue ) ;
304 | runtime.references.targets.push( targetValue ) ;
305 | }
306 |
307 | if ( runtime.unflatKeys && runtime.unflatIndex < runtime.unflatKeys.length - 1 ) {
308 | // Finish unflatting this property
309 | let nextSourceKey = runtime.unflatKeys[ runtime.unflatIndex + 1 ] ;
310 |
311 | extendOneKV(
312 | {
313 | depth: runtime.depth , // keep the same depth
314 | unflatKeys: runtime.unflatKeys ,
315 | unflatIndex: runtime.unflatIndex + 1 ,
316 | unflatFullKey: runtime.unflatFullKey ,
317 | prefix: '' ,
318 | references: runtime.references
319 | } ,
320 | options , targetValue , source , nextSourceKey , mask
321 | ) ;
322 | }
323 | else {
324 | // Recursively extends sub-object
325 | extendOne(
326 | {
327 | depth: runtime.depth + 1 ,
328 | prefix: '' ,
329 | references: runtime.references
330 | } ,
331 | options , targetValue , sourceValue , mask
332 | ) ;
333 | }
334 | }
335 | }
336 | else if ( mask && ( targetValue === undefined || targetValueIsObject || sourceValueIsObject ) ) {
337 | // Do not create new value, and so do not delete source's properties that were not moved.
338 | // We also do not overwrite object with non-object, and we don't overwrite non-object with object (preserve hierarchy)
339 | return ;
340 | }
341 | else if ( options.preserve && targetValue !== undefined ) {
342 | // Do not overwrite, and so do not delete source's properties that were not moved
343 | return ;
344 | }
345 | else if ( ! options.inherit ) {
346 | if ( options.descriptor ) { Object.defineProperty( target , targetKey , sourceDescriptor ) ; }
347 | else { target[ targetKey ] = targetValue = sourceValue ; }
348 | }
349 |
350 | // Delete owned property of the source object
351 | if ( options.move ) { delete source[ sourceKey ] ; }
352 | }
353 |
354 |
--------------------------------------------------------------------------------
/lib/lazy.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | /*
32 | DEPRECATED, moved to its own module: lazyness
33 | */
34 |
35 | exports.defineLazyProperty = function defineLazyProperty( object , name , func ) {
36 | Object.defineProperty( object , name , {
37 | configurable: true ,
38 | enumerable: true ,
39 | get: function() {
40 |
41 | var value = func() ;
42 |
43 | Object.defineProperty( object , name , {
44 | configurable: true ,
45 | enumerable: true ,
46 | writable: false ,
47 | value: value
48 | } ) ;
49 |
50 | return value ;
51 | }
52 | } ) ;
53 | } ;
54 |
55 |
--------------------------------------------------------------------------------
/lib/mask.js:
--------------------------------------------------------------------------------
1 | /*
2 | Tree Kit
3 |
4 | Copyright (c) 2014 - 2021 Cédric Ronvel
5 |
6 | The MIT License (MIT)
7 |
8 | Permission is hereby granted, free of charge, to any person obtaining a copy
9 | of this software and associated documentation files (the "Software"), to deal
10 | in the Software without restriction, including without limitation the rights
11 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 | copies of the Software, and to permit persons to whom the Software is
13 | furnished to do so, subject to the following conditions:
14 |
15 | The above copyright notice and this permission notice shall be included in all
16 | copies or substantial portions of the Software.
17 |
18 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 | SOFTWARE.
25 | */
26 |
27 | "use strict" ;
28 |
29 |
30 |
31 | const tree = require( './tree.js' ) ;
32 | const util = require( 'util' ) ;
33 |
34 |
35 |
36 | // Create and export
37 | const masklib = {} ;
38 | module.exports = masklib ;
39 |
40 |
41 |
42 | /*
43 | == Mask-family class ==
44 |
45 | Recursively select values in the input object if the same path in the mask object is set.
46 | */
47 |
48 | /*
49 | TODO:
50 | - negative mask
51 | - constraint check
52 | - Maskable object, like in csk-php
53 | */
54 |
55 | masklib.Mask = function Mask() {
56 | throw new Error( 'Cannot create a tree.Mask() directly' ) ;
57 | } ;
58 |
59 |
60 |
61 | const maskDefaultOptions = {
62 | clone: false ,
63 | path: '