What a wonderful detour over docco, only to recognize that it was always possible to auto generate documentation.

... if one would just define the correct folder...
at least deep knowledge about docco now, plus that it doesn't use glob (leaves it a user task...) while groc does use glob to resolve file paths, yay!
This commit is contained in:
Dominic Bosch 2013-11-20 15:41:41 +01:00
parent 12b5088d5e
commit 8c115aa71d
11 changed files with 413 additions and 236 deletions

View file

@ -1,25 +0,0 @@
/*
* LICENSE
* =======
* Copyright (c) 2013 by Dominic Bosch and The
* Regents of the University of Basel. All rights reserved.
*
* Permission to use, copy, modify, and distribute this software and its
* documentation for any purpose, without fee, and without written agreement is
* hereby granted, provided that the above copyright notice and the following
* two paragraphs appear in all copies of this software.
*
* IN NO EVENT SHALL THE UNIVERSITY OF BASEL BE LIABLE TO ANY PARTY FOR
* DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
* OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
* BASEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
* THE UNIVERSITY OF BASEL SPECIFICALLY DISCLAIMS ANY WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
* ON AN "AS IS" BASIS, AND THE UNIVERSITY OF BASEL HAS NO OBLIGATION TO
* PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
*
* Authors: Dominic Bosch <dominic.bosch@stud.unibas.ch>
*
*/

22
LICENSE.md Normal file
View file

@ -0,0 +1,22 @@
LICENSE
=======
Copyright (c) 2013 by Dominic Bosch and The
Regents of the University of Basel. All rights reserved.
Permission to use, copy, modify, and distribute this software and its
documentation for any purpose, without fee, and without written agreement is
hereby granted, provided that the above copyright notice and the following
two paragraphs appear in all copies of this software.
IN NO EVENT SHALL THE UNIVERSITY OF BASEL BE LIABLE TO ANY PARTY FOR
DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES ARISING OUT
OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF THE UNIVERSITY OF
BASEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
THE UNIVERSITY OF BASEL SPECIFICALLY DISCLAIMS ANY WARRANTIES,
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
ON AN "AS IS" BASIS, AND THE UNIVERSITY OF BASEL HAS NO OBLIGATION TO
PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
Authors: Dominic Bosch <dominic.bosch@stud.unibas.ch>

View file

@ -17,25 +17,24 @@ Prerequisites:
- node.js & npm (find it [here](http://nodejs.org/))
- *(optional) coffee, if you want to compile from coffee sources:*
sudo npm -g install coffee-script
sudo npm -g install coffee-script
Clone project:
git clone https://github.com/dominicbosch/webapi-eca.git
git clone https://github.com/dominicbosch/webapi-eca.git
Download and install dependencies:
cd webapi-eca
npm install
Get your [redis](http://redis.io/) instance up and running (and find the port for the config file below) or create your own `db_interface.js`.
Get your [redis](http://redis.io/) instance up and running (and find the port for the config file below) or create your own `js/db_interface.js`.
Create the configuration file:
Edit the configuration file:
mkdir config
vi config/config.json
Insert your settings, for example:
Apply your settings, for example:
{
"http_port": 8125,

73
coffee/config.coffee Normal file
View file

@ -0,0 +1,73 @@
'use strict'
path = require 'path'
log = require './logging'
fs = require 'fs'
config = null
###
Calling the module as a function will make it look for the `relPath` property in the
args object and then try to load a config file from that relative path.
@param {Object} args
###
exports = module.exports = (args) ->
args = args ? {}
log args
if typeof args.relPath is 'string'
loadConfigFiles args.relPath
module.exports
###
@Function loadConfigFile
Tries to load a configuration file from the path relative to this module's parent folder.
@param {String} relPath
###
loadConfigFile = (relPath) ->
try
### We read the config file synchronously from the file system and try to parse it ###
config = JSON.parse fs.readFileSync path.resolve __dirname, '..', relPath
if config and config.http_port and config.db_port and
config.crypto_key and config.session_secret
log.print 'CF', 'config file loaded successfully'
else
log.error 'CF', new Error("""Missing property in config file, requires:
- http_port
- db_port
- crypto_key
- session_secret""")
catch e
e.addInfo = 'no config ready'
log.error 'CF', e
loadConfigFile path.join('config', 'config.json')
### Answer true if the config file is ready, else false ###
exports.isReady = -> config?
###
Fetch a property from the configuration
@param {String} prop
###
fetchProp = (prop) -> config?[prop]
###
Get the HTTP port
###
exports.getHttpPort = -> fetchProp 'http_port'
###
Get the DB port
###
exports.getDBPort = -> fetchProp 'db_port'
###
Get the crypto key
###
exports.getCryptoKey = -> fetchProp 'crypto_key'
###
Get the session secret
###
exports.getSessionSecret = -> fetchProp 'session_secret'

View file

@ -2,23 +2,25 @@
###
Rules Server
============
This is the main module that is used to run the whole server:
node server [log_type http_port]
Valid `log_type`'s are:
- `0`: standard I/O output (default)
- `1`: log file (server.log)
- `2`: silent
`http_port` can be set to use another port, than defined in the
[config](config.html) file, to listen to, e.g. used by the test suite.
>This is the main module that is used to run the whole server:
>
> node server [log_type http_port]
>
>Valid `log_type`'s are:
>
>- `0`: standard I/O output (default)
>- `1`: log file (server.log)
>- `2`: silent
>
>`http_port` can be set to use another port, than defined in the
>[config](config.html) file, to listen to, e.g. used by the test suite.
>
>---
###
'use strict'
### Grab all required modules ###
path = require 'path'
log = require './logging'
conf = require './config'
@ -28,8 +30,9 @@ http_listener = require './http_listener'
mm = require './module_manager'
args = {}
procCmds = {}
### Prepare the admin commands that are issued via HTTP requests. ###
adminCmds = {
### Prepare the admin commands that are issued via HTTP requests. ###
adminCmds =
'loadrules': mm.loadRulesFromFS,
'loadaction': mm.loadActionModuleFromFS,
'loadactions': mm.loadActionModulesFromFS,
@ -37,7 +40,7 @@ adminCmds = {
'loadevents': mm.loadEventModulesFromFS,
'loadusers': http_listener.loadUsers,
'shutdown': shutDown
};
###
Error handling of the express port listener requires special attention,
@ -53,7 +56,6 @@ process.on 'uncaughtException', (err) ->
else log.error err
null
###
## Initialize the Server
This function is invoked right after the module is loaded and starts the server.
@ -61,11 +63,13 @@ This function is invoked right after the module is loaded and starts the server.
init = ->
log.print 'RS', 'STARTING SERVER'
### Check whether the config file is ready, which is required to start the server. ###
if !conf.isReady()
log.error 'RS', 'Config file not ready!'
process.exit
process.exit()
### Fetch the `log_type` argument and post a log about which log type is used. ###
### Fetch the `log_type` argument and post a log about which log type is used.###
if process.argv.length > 2
args.logType = parseInt(process.argv[2]) || 0
switch args.logType
@ -73,48 +77,63 @@ init = ->
when 1 then log.print 'RS', 'Log type set to file output'
when 2 then log.print 'RS', 'Log type set to silent'
else log.print 'RS', 'Unknown log type, using standard I/O'
log(args);
log args
else
log.print 'RS', 'No log method argument provided, using standard I/O'
### Fetch the `http_port` argument ###
if process.argv.length > 3 then args.http_port = parseInt(process.argv[3]);
if process.argv.length > 3 then args.http_port = parseInt process.argv[3]
else log.print 'RS', 'No HTTP port passed, using standard port from config file'
### Initialize all required modules with the args object. ###
db args
db.isConnected((err, result) -> if !err then continueInit())
### We only proceed with the initialization if the DB is ready ###
db.isConnected (err, result) ->
if !err
### Initialize all required modules with the args object.###
log.print 'RS', 'Initialzing engine'
engine args
log.print 'RS', 'Initialzing http listener'
http_listener args
log.print 'RS', 'Initialzing module manager'
mm args
log.print 'RS', 'Initialzing DB'
continueInit = ->
log.print 'RS', 'Initialzing engine'
engine(args)
log.print 'RS', 'Initialzing http listener'
http_listener(args)
log.print 'RS', 'Initialzing module manager'
mm(args)
log.print 'RS', 'Initialzing DB'
### Distribute handlers between modules to link the application. ###
log.print 'RS', 'Passing handlers to engine'
engine.addDBLinkAndLoadActionsAndRules db
log.print 'RS', 'Passing handlers to http listener'
http_listener.addHandlers db, handleAdminCommands, engine.pushEvent
log.print 'RS', 'Passing handlers to module manager'
mm.addHandlers db, engine.loadActionModule, engine.addRule
null
### Distribute handlers between modules to link the application. ###
log.print 'RS', 'Passing handlers to engine'
engine.addDBLinkAndLoadActionsAndRules db
log.print 'RS', 'Passing handlers to http listener'
http_listener.addHandlers db, handleAdminCommands, engine.pushEvent
log.print 'RS', 'Passing handlers to module manager'
mm.addHandlers db, engine.loadActionModule, engine.addRule
###
admin commands handler receives all command arguments and an answerHandler
object that eases response handling to the HTTP request issuer.
###
handleAdminCommands = (args, answHandler) ->
if args and args.cmd then adminCmds[args.cmd]?(args, answHandler)
else log.print 'RS', 'No command in request'
fAnsw = (ah) -> ->
if not ah.isAnswered() then answHandler.answerError 'Not handled...'
if args and args.cmd
adminCmds[args.cmd]? args, answHandler
else
log.print 'RS', 'No command in request'
###
The fAnsw function receives an answerHandler object as an argument when called
and returns an anonymous function
###
fAnsw = (ah) ->
###
The anonymous function checks whether the answerHandler was already used to
issue an answer, if no answer was provided we answer with an error message
###
() ->
if not ah.isAnswered()
ah.answerError 'Not handled...'
###
Delayed function call of the anonymous function that checks the answer handler
###
setTimeout fAnsw(answHandler), 2000
null
###
Shuts down the server.
@ -133,11 +152,13 @@ shutDown = (args, answHandler) ->
When the server is run as a child process, this function handles messages
from the parent process (e.g. the testing suite)
###
process.on 'message', (cmd) -> procCmds[cmd]?()
###
The die command redirects to the shutDown function.
###
procCmds.die = shutDown
###

View file

@ -1,13 +1,44 @@
/*
* # docco Documentation
* Create the documentation to be displayed through the webserver.
*/
// var glob = require("glob"),
// docco = require('docco'),
// opt = ["", "", "--output", "webpages/doc"],
// files = [
// "README.md",
// "LICENSE.md",
// "create_doc.js",
// "coffee/*.coffee",
// "mod_actions/**/*.js",
// "mod_events/**/*.js"
// ];
//
// var semaphore = files.length;
// for(var i = 0; i < files.length; i++) {
// glob(files[i], null, function (er, files) {
// if(!er) {
// opt = opt.concat(files);
// } else {
// console.error(er);
// }
// if(--semaphore === 0) {
// docco.run(opt);
// }
// });
// }
/*
* # groc Documentation
* Create the documentation to be displayed through the webserver.
*/
*/
//
require('groc').CLI(
[
"README.md",
"TODO.js",
"LICENSE.js",
"js-coffee/*",
"LICENSE.md",
"coffee/*.coffee",
"mod_actions/**/*.js",
"mod_events/**/*.js",
"-o./webpages/doc"

View file

@ -1,80 +1,111 @@
'use strict';
// Generated by CoffeeScript 1.6.3
(function() {
'use strict';
var config, exports, fetchProp, fs, loadConfigFile, log, path;
var path = require('path'),
log = require('./logging'),
config;
path = require('path');
exports = module.exports = function(args) {
args = args || {};
log(args);
if(typeof args.relPath === 'string') loadConfigFile(args.relPath);
//TODO check all modules whether they can be loaded without calling the module.exports with args
return module.exports;
};
log = require('./logging');
loadConfigFile(path.join('config', 'config.json'));
fs = require('fs');
function loadConfigFile(relPath) {
try {
config = JSON.parse(require('fs').readFileSync(path.resolve(__dirname, '..', relPath)));
if(config && config.http_port && config.db_port
&& config.crypto_key && config.session_secret) {
log.print('CF', 'config file loaded successfully!');
} else {
log.error('CF', new Error('Missing property in config file, requires:\n'
+ ' - http_port\n'
+ ' - db_port\n'
+ ' - crypto_key\n'
+ ' - session_secret'));
config = null;
/*
Calling the module as a function will make it look for the `relPath` property in the
args object and then try to load a config file from that relative path.
@param {Object} args
*/
exports = module.exports = function(args) {
args = args != null ? args : {};
log(args);
if (typeof args.relPath === 'string') {
loadConfigFiles(args.relPath);
}
} catch (e) {
e.addInfo = 'no config ready';
log.error('CF', e);
}
}
return module.exports;
};
/**
* Answer true if the config file is ready, else false
*/
exports.isReady = function() {
if(config) return true;
else return false;
};
/*
@Function loadConfigFile
Tries to load a configuration file from the path relative to this module's parent folder.
@param {String} relPath
*/
/**
* Fetch a property from the configuration
* @param {String} prop
*/
function fetchProp(prop) {
if(config) return config[prop];
}
/**
* Get the HTTP port
*/
exports.getHttpPort = function() {
return fetchProp('http_port');
};
loadConfigFile = function(relPath) {
var e;
try {
/* We read the config file synchronously from the file system and try to parse it*/
/**
* Get the DB port
*/
exports.getDBPort = function() {
return fetchProp('db_port');
};
config = JSON.parse(fs.readFileSync(path.resolve(__dirname, '..', relPath)));
if (config && config.http_port && config.db_port && config.crypto_key && config.session_secret) {
return log.print('CF', 'config file loaded successfully');
} else {
return log.error('CF', new Error("Missing property in config file, requires:\n- http_port\n- db_port\n- crypto_key\n- session_secret"));
}
} catch (_error) {
e = _error;
e.addInfo = 'no config ready';
return log.error('CF', e);
}
};
/**
* Get the crypto key
*/
exports.getCryptoKey = function() {
return fetchProp('crypto_key');
};
loadConfigFile(path.join('config', 'config.json'));
/**
* Get the session secret
*/
exports.getSessionSecret = function() {
return fetchProp('session_secret');
};
/* Answer true if the config file is ready, else false*/
exports.isReady = function() {
return config != null;
};
/*
Fetch a property from the configuration
@param {String} prop
*/
fetchProp = function(prop) {
return config != null ? config[prop] : void 0;
};
/*
Get the HTTP port
*/
exports.getHttpPort = function() {
return fetchProp('http_port');
};
/*
Get the DB port
*/
exports.getDBPort = function() {
return fetchProp('db_port');
};
/*
Get the crypto key
*/
exports.getCryptoKey = function() {
return fetchProp('crypto_key');
};
/*
Get the session secret
*/
exports.getSessionSecret = function() {
return fetchProp('session_secret');
};
}).call(this);

View file

@ -2,24 +2,28 @@
/*
Rules Server
============
This is the main module that is used to run the whole server:
node server [log_type http_port]
Valid `log_type`'s are:
- `0`: standard I/O output (default)
- `1`: log file (server.log)
- `2`: silent
`http_port` can be set to use another port, than defined in the
[config](config.html) file, to listen to, e.g. used by the test suite.
>This is the main module that is used to run the whole server:
>
> node server [log_type http_port]
>
>Valid `log_type`'s are:
>
>- `0`: standard I/O output (default)
>- `1`: log file (server.log)
>- `2`: silent
>
>`http_port` can be set to use another port, than defined in the
>[config](config.html) file, to listen to, e.g. used by the test suite.
>
>---
*/
(function() {
'use strict';
var adminCmds, args, conf, continueInit, db, engine, handleAdminCommands, http_listener, init, log, mm, path, procCmds, shutDown;
/* Grab all required modules*/
var adminCmds, args, conf, db, engine, handleAdminCommands, http_listener, init, log, mm, path, procCmds, shutDown;
path = require('path');
@ -80,9 +84,11 @@ Valid `log_type`'s are:
init = function() {
log.print('RS', 'STARTING SERVER');
/* Check whether the config file is ready, which is required to start the server.*/
if (!conf.isReady()) {
log.error('RS', 'Config file not ready!');
process.exit;
process.exit();
}
/* Fetch the `log_type` argument and post a log about which log type is used.*/
@ -112,35 +118,32 @@ Valid `log_type`'s are:
} else {
log.print('RS', 'No HTTP port passed, using standard port from config file');
}
/* Initialize all required modules with the args object.*/
db(args);
/* We only proceed with the initialization if the DB is ready*/
return db.isConnected(function(err, result) {
if (!err) {
return continueInit();
/* Initialize all required modules with the args object.*/
log.print('RS', 'Initialzing engine');
engine(args);
log.print('RS', 'Initialzing http listener');
http_listener(args);
log.print('RS', 'Initialzing module manager');
mm(args);
log.print('RS', 'Initialzing DB');
/* Distribute handlers between modules to link the application.*/
log.print('RS', 'Passing handlers to engine');
engine.addDBLinkAndLoadActionsAndRules(db);
log.print('RS', 'Passing handlers to http listener');
http_listener.addHandlers(db, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Passing handlers to module manager');
return mm.addHandlers(db, engine.loadActionModule, engine.addRule);
}
});
};
continueInit = function() {
log.print('RS', 'Initialzing engine');
engine(args);
log.print('RS', 'Initialzing http listener');
http_listener(args);
log.print('RS', 'Initialzing module manager');
mm(args);
log.print('RS', 'Initialzing DB');
/* Distribute handlers between modules to link the application.*/
log.print('RS', 'Passing handlers to engine');
engine.addDBLinkAndLoadActionsAndRules(db);
log.print('RS', 'Passing handlers to http listener');
http_listener.addHandlers(db, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Passing handlers to module manager');
mm.addHandlers(db, engine.loadActionModule, engine.addRule);
return null;
};
/*
admin commands handler receives all command arguments and an answerHandler
object that eases response handling to the HTTP request issuer.
@ -156,15 +159,28 @@ Valid `log_type`'s are:
} else {
log.print('RS', 'No command in request');
}
/*
The fAnsw function receives an answerHandler object as an argument when called
and returns an anonymous function
*/
fAnsw = function(ah) {
/*
The anonymous function checks whether the answerHandler was already used to
issue an answer, if no answer was provided we answer with an error message
*/
return function() {
if (!ah.isAnswered()) {
return answHandler.answerError('Not handled...');
return ah.answerError('Not handled...');
}
};
};
setTimeout(fAnsw(answHandler), 2000);
return null;
/*
Delayed function call of the anonymous function that checks the answer handler
*/
return setTimeout(fAnsw(answHandler), 2000);
};
/*

View file

@ -8,7 +8,6 @@ exports = module.exports = function(args) {
args = args || {};
log(args);
if(typeof args.relPath === 'string') loadConfigFile(args.relPath);
//TODO check all modules whether they can be loaded without calling the module.exports with args
return module.exports;
};

View file

@ -1,27 +1,44 @@
/*
* Rules Server
* ============
* This is the main module that is used to run the whole server:
*
* node server [log_type http_port]
*
* Valid `log_type`'s are:
*
* - `0`: standard I/O output (default)
* - `1`: log file (server.log)
* - `2`: silent
*
* `http_port` can be set to use another port, than defined in the
* [config](config.html) file, to listen to, e.g. used by the test suite.
* >This is the main module that is used to run the whole server:
* >
* > node server [log_type http_port]
* >
* >Valid `log_type`'s are:
* >
* >- `0`: standard I/O output (default)
* >- `1`: log file (server.log)
* >- `2`: silent
* >
* >`http_port` can be set to use another port, than defined in the
* >[config](config.html) file, to listen to, e.g. used by the test suite.
* >
* >---
*/
'use strict';
// Grab all required modules
var path = require('path'),
log = require('./logging'),
conf = require('./config'),
http_listener = require('./http_listener'),
mm = require('./module_manager'),
db = require('./db_interface'),
engine = require('./engine'),
semaphore = 0,
args = {},
procCmds = {},
adminCmds, http_listener, mm, db, engine;
// Prepare the admin commands that are issued via HTTP requests.
adminCmds = {
'loadrules': mm.loadRulesFromFS,
'loadaction': mm.loadActionModuleFromFS,
'loadactions': mm.loadActionModulesFromFS,
'loadevent': mm.loadEventModuleFromFS,
'loadevents': mm.loadEventModulesFromFS,
'loadusers': http_listener.loadUsers,
'shutdown': shutDown
};
/*
* Error handling of the express port listener requires special attention,
@ -40,15 +57,15 @@ process.on('uncaughtException', function(err) {
});
/**
* ### Initialize the Server
* ## Initialize the Server
* This function is invoked right after the module is loaded and starts the server.
*/
function init() {
log.print('RS', 'STARTING SERVER');
// Check whether the config file is ready, which is required to start the server.
if(!require('./config').isReady()) {
if(!conf.isReady()) {
log.error('RS', 'Config file not ready!');
return;
process.exit();
}
// Fetch the `log_type` argument and post a log about which log type is used.
@ -65,41 +82,31 @@ function init() {
if(process.argv.length > 3) args.http_port = parseInt(process.argv[3]);
else log.print('RS', 'No HTTP port passed, using standard port from config file');
// Initialize all required modules with the args object.
db = require('./db_interface')(args);
db(args);
// We only proceed with the initialization if the DB is ready
db.isConnected(function(err, result) {
if(!err) continueInit();
if(!err) {
// Initialize all required modules with the args object.
log.print('RS', 'Initialzing engine');
engine(args);
log.print('RS', 'Initialzing http listener');
http_listener(args);
log.print('RS', 'Initialzing module manager');
mm(args);
log.print('RS', 'Initialzing DB');
// Distribute handlers between modules to link the application.
log.print('RS', 'Passing handlers to engine');
engine.addDBLinkAndLoadActionsAndRules(db);
log.print('RS', 'Passing handlers to http listener');
http_listener.addHandlers(db, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Passing handlers to module manager');
mm.addHandlers(db, engine.loadActionModule, engine.addRule);
}
});
}
function continueInit() {
log.print('RS', 'Initialzing engine');
engine = require('./engine')(args);
log.print('RS', 'Initialzing http listener');
http_listener = require('./http_listener')(args);
log.print('RS', 'Initialzing module manager');
mm = require('./module_manager')(args);
log.print('RS', 'Initialzing DB');
// Load the admin commands that are issued via HTTP requests.
adminCmds = {
'loadrules': mm.loadRulesFromFS,
'loadaction': mm.loadActionModuleFromFS,
'loadactions': mm.loadActionModulesFromFS,
'loadevent': mm.loadEventModuleFromFS,
'loadevents': mm.loadEventModulesFromFS,
'loadusers': http_listener.loadUsers,
'shutdown': shutDown
};
// Distribute handlers between modules to link the application.
log.print('RS', 'Passing handlers to engine');
engine.addDBLinkAndLoadActionsAndRules(db);
log.print('RS', 'Passing handlers to http listener');
http_listener.addHandlers(db, handleAdminCommands, engine.pushEvent);
log.print('RS', 'Passing handlers to module manager');
mm.addHandlers(db, engine.loadActionModule, engine.addRule);
}
/**
* admin commands handler receives all command arguments and an answerHandler

View file

@ -10,7 +10,9 @@
},
"dependencies": {
"connect-redis": "1.4.6",
"docco": "0.6.2",
"express": "3.4.0",
"glob" :"3.2.7",
"groc": "0.6.1",
"needle": "0.6.1",
"nodeunit": "0.8.2",
@ -19,6 +21,7 @@
},
"__comment": {
"dependencies": {
"groc": "0.6.1",
"diff": "1.0.5",
"socket.io": "0.9.16",
"contextify": "0.1.6"