Skip to content

Commit

Permalink
Initial
Browse files Browse the repository at this point in the history
  • Loading branch information
benw committed Sep 20, 2012
0 parents commit 072d3d8
Show file tree
Hide file tree
Showing 5 changed files with 303 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
@@ -0,0 +1 @@
node_modules
Empty file added README.md
Empty file.
49 changes: 49 additions & 0 deletions bin/rhizome
@@ -0,0 +1,49 @@
#!/usr/bin/env node

var rhizome = require('..');
var pegjs = require('pegjs');
var fs = require('fs');

var usage = 'Usage: rhizome routes.pegjs [/uri/path/to/test]';

var filename = process.argv[2];
var uri = process.argv[3];

if (process.argv.length < 3) {
console.log(usage);
process.exit(1);
}


var grammar = fs.readFileSync(filename, 'utf8');

try {
var parser = pegjs.buildParser(grammar);
}
catch (e) {
console.log(e);
process.exit(2);
}

var config = {
parser: parser,
debug: true
};

// var router = rhizome(config);

if (uri) {
var parsetree = rhizome.parse(config, uri + '#');
var leaf = rhizome.resolve(parsetree).node;
console.log(leaf);
}

// var rhizome = require('rhizome');
// var resources = require('./resources');
// var grammar = 'a valid peg.js grammar';
//
//
// app.use(rhizome({
// parser: parser,
// resources: resources
// }));
227 changes: 227 additions & 0 deletions index.js
@@ -0,0 +1,227 @@
var util = require('util');
var url = require('url');

//
// Rhizome
//
// A connect/express middleware for dispatching requests to a
// hierarchy of resources, using a grammar to describe the
// structure of the url hierarchy.
//
// Synopsis:
//
// var rhizome = require('rhizome');
// var pegjs = require('pegjs');
// var resources = require('./resources');
// var grammar = 'a valid peg.js grammar';
//
// var parser = pegjs.buildParser(grammar);
//
// app.use(rhizome({
// parser: parser,
// resources: resources
// }));
//
// You construct a pegjs parser from the given grammar.
// Then for each request passed to this middleware, the parser
// is given req.url as input, and the resulting parse tree is
// traversed to resolve the request to a resource. See also the
// comments for rhizome.resolve, below.
//
// config fields:
//
// parser A pegjs-compatible parser object,
// having parser.parse(input, [startRule])
//
// resources An object holding resource constructors.
// tree resolves a leaf resource.
//
// [makeinput] Function to extract from a request the input
// to be parsed. Defaults to rhizome.makeinput
//
// [startRule] The name of the start rule in the grammar.
// Defaults to the first declared rule.
//
//
// [parse] Function to parse a request into a parse tree.
// Defaults to rhizome.parse.
//
// [dispatch] Function to dispatch a request against
// a resource. Defaults to rhizome.dispatch.
//
// [debug] True to log parse errors. Defaults to false.
//
var rhizome = module.exports = function rhizome(config) {
var parse = config.parse || rhizome.parse;
var dispatch = config.dispatch || rhizome.dispatch;
var makeinput = config.makeinput || rhizome.makeinput;
function getResource(base, selector) {
var input = url.resolve(base, selector);
var tree = parse(config, input);
if (!tree) {
return null;
}
var result = rhizome.resolve(tree);
if (!result.node) {
return null;
}
var path = result.suffix ?
input.slice(0, -result.suffix.length) :
input;
var type = result.node.type;
var Constructor = config.resources[type];
if (!(Constructor && Constructor instanceof Function)) {
throw new TypeError('Unknown resource type ' +
util.inspect(type));
}
function select(relpath) {
var result = getResource(path + '/', relpath);
return result ? result.resource : null;
}
result.resource = new Constructor(path, result.node, select);
return result;
}
return function (req, res, next) {
var result = getResource('', makeinput(req), req);
if (result && result.resource) {
dispatch(config, result, req, res, next);
} else {
next();
}

};
};

//
// rhizome.makeinput
//
// The user may replace this implementation by providing config.makeinput,
// for example to construct input based on req.hostname and req.url.
//
rhizome.makeinput = function makeinput(req) {
return req.url + '#';
};

//
// rhizome.parse
//
// Parse a url and return a parse tree.
//
// This default implementation is suitable for pegjs parsers.
// If a parser syntax error occurs, it returns null to indicate
// that the url did not match the grammar.
//
rhizome.parse = function parse(config, input) {
var parser = config.parser;
var tree;
try {
tree = parser.parse(input, config.startRule);
}
catch (e) {
// pegjs provides parser.SyntaxError, but we try
// to be compatible with other parsers that might not.
if (parser.SyntaxError && (e instanceof parser.SyntaxError)) {
if (config.debug) {
var marker = '';
if (e.column) {
for (var i = e.column; i > 1; i--) {
marker += ' ';
}
marker += '^ ';
}
console.log(input + '\n' + marker);
console.log(e);
}
return null;
}
throw e;
}
return tree;
};

//
// rhizome.dispatch
//
// Dispatch a request against the resolved resource object.
//
// It looks for a method on the resource object having the same name as the
// http verb of the request, i.e. resource.get, resource.post etc, or
// resource.all as a fallback. If that method exists it is called, e.g:
//
// resource.get(req, res, next, suffix);
//
// Arguments:
//
// config The config object that was passed to rhizome.
//
// result The result of resolving against the parse tree.
// result.resource is the resource object.
// result.suffix is the url portion following the resource's path.
//
// req,res,next The request and response objects and the next function passed
// to this middleware for this request. Each call to dispatch
// should normally result eventually in a call to res.end (via
// res.send, res.render etc) or next(err).
//
//
rhizome.dispatch = function (config, result, req, res, next) {
var resource = result.resource;
var suffix = result.suffix;
if (!resource) {
// No error but resolved to a null resource - fall through silently.
if (config.debug) {
console.log(req.url + ' resolved to null resource');
}
return next();
}
if ('object' !== typeof resource) {
return next(new TypeError('resource is not an object'));
}
var fn = resource[req.method.toLowerCase()] || resource.all;
if ('function' === typeof fn) {
fn.call(resource, req, res, next, suffix);
} else {
res.send('Resource at ' + req.url +
' does not implement ' + req.method + '\r\n', 404);
}
};

//
// rhizome.resolve
//
// Resolve from resourceRoot to a resource selected by the parse tree.
//
// The parser returns a parse tree representing the url. This is
// a tree of nested arrays.
//
// Returns:
//
// {
// node: // the last object in the tree, or null if none.
// suffix: // string component of the url following node
// }
//
rhizome.resolve = function resolve(tree) {
if (Array.isArray(tree)) {
var node;
var suffix = '';
for (var i = tree.length - 1; !node && i >= 0; i--) {
var branch = resolve(tree[i]);
node = branch.node;
suffix = branch.suffix + suffix;
}
return {
node: node,
suffix: suffix
};
} else if ('string' === typeof tree) {
return {
suffix: tree
};
} else {
return {
node: tree,
suffix: ''
};
}
};
26 changes: 26 additions & 0 deletions package.json
@@ -0,0 +1,26 @@
{
"name": "rhizome",
"version": "0.1.0",
"description": "Use a peg.js grammar to route url requests",
"author": {
"name": "Ben Williamson",
"email": "benw@pobox.com"
},
"license": "MIT",
"main": "index.js",
"scripts": {
"test": "node_modules/mocha/bin/mocha"
},
"repository": {
"type": "git",
"url": "git://github.com/benw/rhizome.git"
},
"dependencies": {
"pegjs": "0.7.x"
},
"devDependencies": {
"mocha": "1.4.x",
"supertest": "0.2.x",
"chai": "1.2.x"
}
}

0 comments on commit 072d3d8

Please sign in to comment.