tree-climber
Advanced tools
Comparing version
@@ -5,2 +5,3 @@ 'use strict'; | ||
var Promise = require('promise'); | ||
var format = require('util').format; | ||
@@ -17,6 +18,9 @@ module.exports = { | ||
* @param {Function} visitor | ||
* @param {String} [sep] | ||
*/ | ||
function climb (tree, visitor) { | ||
function climb (tree, visitor, sep) { | ||
var visited = []; | ||
sep = sep || '.'; | ||
(function climber (value, key, path) { | ||
@@ -31,3 +35,3 @@ if (isNode(value)) { | ||
edgeTraverser(value, function handleEdge (childValue, key) { | ||
climber(childValue, key, calculatePath(path, key)); | ||
climber(childValue, key, calculatePath(path, key, sep)); | ||
}); | ||
@@ -47,54 +51,60 @@ } | ||
* @param {Function} visitor | ||
* @param {String} [sep] | ||
* @returns {Promise} A promise fulfilled when all nodes have been resolved. | ||
*/ | ||
function climbAsync (tree, visitor) { | ||
function climbAsync (tree, visitor, sep) { | ||
var allPromises = []; | ||
var paths = {}; | ||
sep = sep || '.'; | ||
climb(tree, function (key, value, path) { | ||
var boundVisitor = visitor.bind(null, key, value, path); | ||
var pathParts = path.split('.').slice(0, -1); | ||
pathParts.unshift('_ROOT'); | ||
var pathToNode = pathParts.join('.'); | ||
var newPromise; | ||
try { | ||
climb(tree, function (key, value, path) { | ||
var boundVisitor = visitor.bind(null, key, value, path); | ||
var pathParts = path.split(sep).slice(0, -1); | ||
pathParts.unshift('_ROOT'); | ||
var pathToNode = pathParts.join(sep); | ||
var newPromise; | ||
Object.keys(paths).sort(sorter).some(function chainOffSlot (storedPath) { | ||
var containsPath = pathToNode.indexOf(storedPath) === 0; | ||
Object.keys(paths).sort(sorter).some(function chainOffSlot (storedPath) { | ||
var containsPath = pathToNode.indexOf(storedPath) === 0; | ||
if (containsPath) | ||
newPromise = paths[storedPath].then(boundVisitor); | ||
if (containsPath) | ||
newPromise = paths[storedPath].then(boundVisitor); | ||
return containsPath; | ||
}); | ||
return containsPath; | ||
}); | ||
pathParts.reduce(function fillEmptySlots (currentPath, part) { | ||
currentPath = (!currentPath ? part : [currentPath, part].join('.')); | ||
pathParts.reduce(function fillEmptySlots (currentPath, part) { | ||
currentPath = (!currentPath ? part : [currentPath, part].join(sep)); | ||
if (!paths[currentPath]) | ||
paths[currentPath] = newPromise || (newPromise = boundVisitor()); | ||
if (!paths[currentPath]) | ||
paths[currentPath] = newPromise || (newPromise = boundVisitor()); | ||
return currentPath; | ||
}, null); | ||
return currentPath; | ||
}, null); | ||
/** | ||
* Sorts the paths from longest to shortest. | ||
* @param {String} a | ||
* @param {String} b | ||
* @returns {Number} | ||
*/ | ||
function sorter (a, b) { | ||
return getPathLength(b) - getPathLength(a); | ||
} | ||
/** | ||
* Sorts the paths from longest to shortest. | ||
* @param {String} a | ||
* @param {String} b | ||
* @returns {Number} | ||
*/ | ||
function sorter (a, b) { | ||
return getPathLength(b) - getPathLength(a); | ||
} | ||
/** | ||
* Returns the number of path separators (.) in a string | ||
* @param {String} path | ||
* @returns {Number} | ||
*/ | ||
function getPathLength (path) { | ||
return path.split('.').length; | ||
} | ||
/** | ||
* Returns the number of path separators (.) in a string | ||
* @param {String} path | ||
* @returns {Number} | ||
*/ | ||
function getPathLength (path) { | ||
return path.split(sep).length; | ||
} | ||
allPromises.push(newPromise); | ||
}); | ||
allPromises.push(newPromise); | ||
}, sep); | ||
} catch (error) { | ||
return Promise.reject(error); | ||
} | ||
@@ -108,9 +118,13 @@ return Promise.all(allPromises); | ||
* @param {String} key | ||
* @param {String} sep | ||
* @returns {String} | ||
*/ | ||
function calculatePath (path, key) { | ||
function calculatePath (path, key, sep) { | ||
if (key.indexOf(sep) !== -1) | ||
throw new Error(format('Key cannot contain a %s character.', sep)); | ||
if (!path) | ||
return key; | ||
else | ||
return path + '.' + key; | ||
return path + sep + key; | ||
} | ||
@@ -117,0 +131,0 @@ |
{ | ||
"name": "tree-climber", | ||
"version": "1.1.0", | ||
"version": "1.2.0", | ||
"description": "Traverses a JavaScript tree like structure. Calls a callback when visiting each node with the key, value, and current tree path.", | ||
@@ -5,0 +5,0 @@ "main": "index.js", |
@@ -65,7 +65,7 @@ Tree Climber | ||
. `obj` {Array|Object} The "tree" to visit each node on. | ||
. `visitor` {Function} Called when visiting a node. | ||
. `key` {String} The key of this node. | ||
. `value` {Mixed} The value of this node. | ||
. `path` {String} The full path of the tree to this node. | ||
* `obj` {Array|Object} The "tree" to visit each node on. | ||
* `visitor` {Function} Called when visiting a node. | ||
* `key` {String} The key of this node. | ||
* `value` {Mixed} The value of this node. | ||
* `path` {String} The full path of the tree to this node. | ||
@@ -75,8 +75,8 @@ tree.climbAsync(obj, visitor) | ||
. `obj` {Array|Object} The "tree" to visit each node on. | ||
. `visitor` {Function} Called when visiting a node. | ||
. `key` {String} The key of this node. | ||
. `value` {Mixed} The value of this node. | ||
. `path` {String} The full path of the tree to this node. | ||
. return: {Promise} A promise to pend resolving other nodes in the tree on. | ||
* `obj` {Array|Object} The "tree" to visit each node on. | ||
* `visitor` {Function} Called when visiting a node. | ||
* `key` {String} The key of this node. | ||
* `value` {Mixed} The value of this node. | ||
* `path` {String} The full path of the tree to this node. | ||
* return: {Promise} A promise to pend resolving other nodes in the tree on. | ||
@@ -83,0 +83,0 @@ Allows the user to perform asynchronous work on each node of the tree. Chains promises |
@@ -107,2 +107,14 @@ 'use strict'; | ||
it('should let the sep be overridden', function () { | ||
climb([ | ||
{ | ||
a: { | ||
b: 'c' | ||
} | ||
} | ||
], visitor, '/'); | ||
expect(visitor).toHaveBeenCalledWith('b', 'c', '0/a/b'); | ||
}); | ||
it('should throw if a cycle is detected', function () { | ||
@@ -123,2 +135,16 @@ var obj = { | ||
}); | ||
it('should throw if a separator is used in the key', function () { | ||
var obj = { | ||
'a.b': { | ||
c: 'd' | ||
} | ||
}; | ||
expect(shouldThrow).toThrow(new Error('Key cannot contain a . character.')); | ||
function shouldThrow () { | ||
climb(obj, visitor); | ||
} | ||
}); | ||
}); | ||
@@ -185,2 +211,18 @@ }); | ||
it('should reject if a separator character is used in a key', function (done) { | ||
var obj = { | ||
'a.b': { | ||
c: 'd' | ||
} | ||
}; | ||
climbAsync(obj, function visitor () { | ||
return Promise.resolve('whatever'); | ||
}) | ||
.catch(function checkVisited (error) { | ||
expect(error).toEqual(new Error('Key cannot contain a . character.')); | ||
}) | ||
.done(done); | ||
}); | ||
it('should visit all the nodes of a complex object in dependency order', function (done) { | ||
@@ -233,2 +275,19 @@ climbAsync(complexObj, function visitor (key) { | ||
}); | ||
it('should not visit any other nodes if f is rejected', function (done) { | ||
climbAsync(complexObj, function visitor (key) { | ||
visited.push(key); | ||
if (key === 'f') | ||
return new Promise(function (resolve, reject) { | ||
reject(); | ||
}); | ||
else | ||
return Promise.resolve('foo'); | ||
}) | ||
.catch(function () { | ||
expect(visited).toEqual(['f']); | ||
}) | ||
.done(done); | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
46269
5.2%392
18.79%0
-100%