Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
felixhageloh committed Aug 13, 2013
0 parents commit f91d373
Show file tree
Hide file tree
Showing 5 changed files with 215 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
56 changes: 56 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
Async tree traversal for nodejs
===============================

***under development!***

<tt>tree-monkey</tt> takes a JSON or object literal tree of the form

{
"nodeA": {
"nodeAA": {
"nodeAAA": {}
}
},
"nodeB": {
"nodeBA": {}
"nodeBB": {}
}
}

and traversers it asynchronously.


## Usage

The basic usage is as follows:

var monkey = require('tree-monkey')
, tree = { ... };

monkey.preOrder(tree, function (node, path, callback) {
// do something async with the current node and/or path
...

// signal that you are done
callback();
});

### Pre-order traversal

Visits all nodes depth-first in async pre-order, meaning each parent node is visited before its child nodes and then all child nodes are visited asynchronously. This means all branches are visited in their own speed, so to say, and we can't know the order in which they will complete.

preOrder(tree, nodeFunction, callback)

Arguments:

<dt><tt>tree</tt></dt> <dd>the tree to traverse</dd>
<dt><tt>nodeFunction</tt></dt> <dd>the function called on each node. Receives the current node, the current path and a callback as arguments. Callback *must* be called eventually.</dd>
<dt><tt>callback</tt></dt> <dd>an optional callback, which is called after the whole tree has been traversed</dd>

### Post-order traversal

Visits all nodes depth-first in async prost-order, meaning children will be visited before their parent node. Child nodes are visited asynchronously, meaning we can't know the order in which they will complete.

preOrder(tree, nodeFunction, callback)

Take the same arguments as <tt>preOrder</tt>
32 changes: 32 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
{
"name": "tree-monkey",
"version": "0.0.1",
"description": "Async tree traversal for nodejs",
"main": "index.js",
"scripts": {
"test": "node_modules/jasmine-node/bin/jasmine-node --coffee specs/"
},
"repository": {
"type": "git",
"url": "https://github.com/felixhageloh/tree-monkey.git"
},
"keywords": [
"async",
"tree",
"traversal",
"directory",
"path"
],
"author": "Felix Hageloh",
"license": "MIT",
"bugs": {
"url": "https://github.com/felixhageloh/tree-monkey/issues"
},
"dependencies": {
"async": "~0.2.9"
},
"devDependencies": {
"coffee-script": "~1.6.3",
"jasmine-node": "~1.11.0"
}
}
82 changes: 82 additions & 0 deletions specs/tree-monkey.spec.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
monkey = require '../src/tree-monkey'

describe 'traverse tree', ->
tree = {
'a': { 'aa': {} }
'b': {
'ba': {},
'bb': { 'bba': {} }
}
}

allPaths = ['', '/a', '/b', '/b/ba', '/b/bb', '/a/aa', '/b/bb/bba']

treeFunction = null

beforeEach ->
# simulate async with a random timeout between 1ms and 100ms
treeFunction = jasmine.createSpy().andCallFake (node, path, callback) ->
setTimeout callback, Math.round(Math.random() * 100)

it 'should traverse a tree pre order', ->
done = false

runs ->
monkey.preOrder tree, treeFunction, -> done = true
waitsFor -> done

runs ->
expect(treeFunction.calls.length).toBe allPaths.length

# collect all paths in the order they were called
paths = []
paths.push call.args[1] for call in treeFunction.calls

# make sure every single path has been visited
for path in allPaths
index = paths.indexOf(path)
console.log path unless index > -1
expect(index).toBeGreaterThan(-1)

# check that pre-order is maintained, i.e parent nodes are
# called before their children
expect(paths.indexOf('')).toBeLessThan paths.indexOf('/a')
expect(paths.indexOf('')).toBeLessThan paths.indexOf('/b')
expect(paths.indexOf('/a')).toBeLessThan paths.indexOf('/a/aa')
expect(paths.indexOf('/b')).toBeLessThan paths.indexOf('/b/ba')
expect(paths.indexOf('/b')).toBeLessThan paths.indexOf('/b/bb')
expect(paths.indexOf('/b/bb')).toBeLessThan paths.indexOf('/b/bb/bba')

it 'should traverse a tree post order', ->
done = false

runs ->
monkey.postOrder tree, treeFunction, -> done = true
waitsFor -> done

runs ->
expect(treeFunction.calls.length).toBe allPaths.length

# collect all paths in the order they were called
paths = []
paths.push call.args[1] for call in treeFunction.calls

# make sure every single path has been visited
for path in allPaths
index = paths.indexOf(path)
console.log path unless index > -1
expect(index).toBeGreaterThan(-1)

# check that post-order is maintained, i.e parent nodes are
# called after their children
expect(paths.indexOf('')).toBeGreaterThan paths.indexOf('/a')
expect(paths.indexOf('')).toBeGreaterThan paths.indexOf('/b')
expect(paths.indexOf('/a')).toBeGreaterThan paths.indexOf('/a/aa')
expect(paths.indexOf('/b')).toBeGreaterThan paths.indexOf('/b/ba')
expect(paths.indexOf('/b')).toBeGreaterThan paths.indexOf('/b/bb')
expect(paths.indexOf('/b/bb')).toBeGreaterThan paths.indexOf('/b/bb/bba')

it 'should not require a final callback', ->
expect( ->
monkey.postOrder tree, (_, __, callback) -> callback()
).not.toThrow()
44 changes: 44 additions & 0 deletions src/tree-monkey.coffee
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
async = require 'async'

preOrderTraverser = (visitNode) ->
visitBranch = (node, path, callback) ->
async.series [
visitNode(node, path),
visitChildren(node, path, visitBranch)
], callback

postOrderTraverser = (visitNode) ->
visitBranch = (node, path, callback) ->
async.series [
visitChildren(node, path, visitBranch),
visitNode(node, path)
], callback

visitChildren = (node, path, visitBranch) -> (callback) ->
visitorFunctions = []

for name, child of node
do (name, child) ->
visitorFunctions.push (done) -> visitBranch child, path+'/'+name, done

async.parallel visitorFunctions, callback

# care for some curry with that?
visitNode = (nodeFunction) -> (node, path) -> (callback) ->
nodeFunction node, path, callback


# visits all nodes depth-first in async pre-order, meaning each parent node
# is visited before its child nodes and then all child nodes are visited
# asynchrounously. This means all branches are visited in their own speed,
# so to say, and we can't know the order in which they will complete.
exports.preOrder = (rootNode, nodeFunction, callback) ->
preOrderTraverser(visitNode(nodeFunction))(rootNode, '', callback)


# visits all nodes depth-first in async prost-order, meaning children will
# be visited before their parent node. Child nodes are visited
# asynchrounously, meaning we can't know the order in which they will
# complete.
exports.postOrder = (rootNode, nodeFunction, callback) ->
postOrderTraverser(visitNode(nodeFunction))(rootNode, '', callback)

0 comments on commit f91d373

Please sign in to comment.