Skip to content

Commit

Permalink
Initial completely untested and non working commit
Browse files Browse the repository at this point in the history
  • Loading branch information
khrome committed Nov 30, 2012
0 parents commit bc863bb
Show file tree
Hide file tree
Showing 3 changed files with 310 additions and 0 deletions.
69 changes: 69 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
protolus-router.js
===========

a lightweight URL router supporting a wide variety of usage patterns.

Creation
--------

var ProtolusRouter = require('protolus-router');
var router = new ProtolusRouter({});

Options are:

1. **argumentNames** : an array of strings to be used as keys when using the regex to function mode
2. **onMissing** : a callback for when no route is found
3. **ini** preload a particular INI file

Function to Function
--------------------
Use two functions, one to match the URL and the other to perform the action, arguments may be handled manually in either function. the action function also may return a value which is passed to the callback of the parse function (if a callback is passed).

router.addRoute(function(url){
//return a truthy value representing whether this url has been selected
}, function(returnedValue){
//use the incoming truthy value to either:
//1) serve the request
//2) return a value which will be passed to .route()s callback
});

Regex to Function
-----------------
Use two functions, one to match the URL and the other to perform the action, arguments may be handled manually in either function. This function may also return a value for the optional parse callback.

router.addRoute(\(users)/([A-Za-z][A-Za-z0-9]{3,16})/([0-9]+)\i, function(user, id, postId){
//serve or return
});

or, if I had set argumentNames = ['user', 'id', 'postId'];

router.addRoute(\(users)/([A-Za-z][A-Za-z0-9]{3,16})/([0-9]+)\i, function(args){
// args = {user:'', id:'', postId:''}
//serve or return
});



INI Parsed
----------
You may also specify an INI file which acts as a list of rewrite rules, each line is of the form:

articles/*/# = "articles?name=*&page=*"

You can either use the option in the constructor or call the INI function with or without a callback(any routes will be queued until the async load is complete)

router.INI('my/awesome/config.ini', function(){
//it's done!
});

This will **always** require the callback to be used on the routed function, which will pass back the routed URL

router.route(url, function(routedURL){
//do stuff here
});

You can mix modes at will.

Enjoy,

-Abbey Hawk Sparrow
33 changes: 33 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
{
"name": "protolus-router",
"homepage": "https://github.com/Protolus/protolus-router",
"version": "0.0.1-alpha",
"main": "./protolus-router.js",
"description": "a lightweight URL router supporting a wide variety of usage patterns",
"keywords": [
"url",
"router",
"protolus",
"ini",
"conf"
],
"author": "Abbey Hawk Sparrow <@khrome> (http://patternweaver.com)",
"contributors": [],
"bugs": {
"url": "https://github.com/Protolus/protolus-router/issues"
},
"license": "MIT",
"repository": {
"type": "git",
"url": "git://github.com/Protolus/protolus-router.git"
},
"dependencies": {
"prime": "*"
},
"devDependencies": {
},
"optionalDependencies": {},
"engines": {
"node": "*"
}
}
208 changes: 208 additions & 0 deletions protolus-router.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
var prime = require('prime');
var type = require('prime/util/type');
var array = require('prime/es5/array');
var fn = require('prime/es5/function');
var regexp = require('prime/es5/regexp');
var Emitter = require('prime/util/emitter');
var fs = require('fs');

var EnhancedEmitter = prime({
inherits: Emitter,
once : function(type, fn){
var ob = this;
function cb(){
ob.off(type, cb);
fn();
}
this.on(type, cb);
}
});

var FileLoader = prime({
loadQueue: [],
loadFileCount: 0,
loadFile : function(type, callback){
this.loadFileCount++;
fs.readFile(fileName, 'utf8', fn.bind(function(data){
this.loadFileCount--;
if(callback) callback(data);
if(this.loadQueue.length > 0 && this.loadFileCount == 0){
var callbacks = this.loadQueue;
this.loadQueue = [];
array.forEach(callbacks, function(callback){
callback();
});
}
}, this));
},
fileLoadComplete : function(callback){
if(this.loadFileCount === 0) callback();
else this.loadQueue.push(callback);
}
});

module.exports = prime({ //the default engine
routes : [],
constructor : function(options){
this.browser = require("browser");
this.options = options;
if(this.options.ini) this.INI(this.options.ini);
},
addRoute : function(test, callback){
if(typeOf(test) == 'string' || typeOf(test) == 'regex'){
var regex;
if(typeOf(test) == 'regex') regex = test;
else regex = new RegExp(test);
if(this.options.argumentNames){
var names = this.options.argumentNames;
test = function(url){
var matches = url.match(regex);
if(matches){
var args = {};
var lastName;
array.forEach(names, function(name, index){
args[name] = matches[index];
lastName = name;
});
return args;
}else return false;
}
}else{
test = function(url){
var matches = url.match(regex);
if(matches) return matches;
else return false;
}
}
}
this.routes.push({
test : test,
callback : callback
});
},
parseINI : function(text, order){
var inQuote = false;
var inComment = false;
var isAssigning = false;
var inGrouping = false;
var buffer = '';
var label = '';
var group = null;
var currentQuoteType = '';
var results = [];
var ch;
for(var lcv=0; lcv < text.length; lcv++){
ch = text[lcv];
if(buffer == '' && ch == '['){
inGrouping = true;
}
if(inGrouping){
if(ch == ']'){
group = buffer;
buffer = '';
inGrouping = false;
}else if(ch != '[') buffer += ch;
continue;
}
if(inComment){
if(ch == "\n"){
inComment = false;
if(isAssigning){
if(group == null){
if(label != '') results[label] = buffer;
}else{
if(!results[group]) results[group] = {};
if(label != '') results[group][label] = buffer;
}
label = buffer = '';
}
}else continue;
}
if(!inQuote && !inComment){ //we're reading chars
if(ch == ';'){
inComment = true;
continue;
}
if(ch == '\'' || ch == '"'){
inQuote = true;
currentQuoteType = ch;
continue;
}
if(!isAssigning && ch == '='){
label = buffer;
buffer = '';
isAssigning = true;
}else{
if(ch == "\n"){
isAssigning = false;
if(group == null){
if(label != '') results[results.length] = {key:label,value:buffer};
}else{
if(results.length == 0 || (results[results.length-1].key != group)) results[results.length] = {key:group,value:[]};
if(label != '') results[results.length-1]['value'][results[results.length-1]['value'].length] = {key:label, value:buffer};
}
label = buffer = '';
}else{
if(ch != ' ') buffer += ch;
}
}
}else{
if(inQuote){ // keep reading until we see our quote end
if(ch == currentQuoteType){
inQuote = false;
}else{
buffer += ch;
}
}
}
}
if(group == null){
if(label != ''){
results[results.length] = {key:label,value:buffer};
}
}else{
if(results.length == 0 || (results[results.length-1].key != group)) results[results.length] = {key:group,value:[]};
if(label != '') results[results.length-1]['value'][results[results.length-1]['value'].length] = {key:label, value:buffer};
}
return results;
},
INI : function(fileName, callback){
this.loadFile(fileName, function(data){
var parseTree = this.parseINI();
var routes = (parseTree && parseTree[0] && parseTree[0].value)?parseTree[0].value:[];
array.each(routes, fn.bind(function(route, index){
if(routes[index].key == '%') routes[index].key = '(.*?)'
else routes[index].key = routes[index].key.replace(/\*/g, '(.*?)').replace('#', '([0-9]*?)');
routes[index].regex = new RegExp('^'+routes[index].key+'$');
count = 1;
var pos = routes[index].value.indexOf('*');
while(pos != -1){
routes[index].value = routes[index].value.substring(0, pos)+'$'+(count++)+routes[index].value.substring(pos+1);
pos = routes[index].value.indexOf('*');
}
this.addRoute(function(url){
if(url.match(routes[index].regex)){
return url.replace(routes[index].regex, routes[index].value);
}else return false;
}, function(routed){
return routed;
});
}));
});
},
route : function(url, callback){
this.fileLoadComplete(fn.bind(function(){
var found;
var result;
array.forEach(this.routes, function(route){
if(found) return;
found = route.test(url);
if(found) result = route.callback.apply(this, (found===false?[]:found));
if(callback) callback(result || url);
});
if(!found && this.options.onMissing) this.options.onMissing(url);
}, this);
},
terminate : function(){
}
}).implement(EnhancedEmitter).implement(FileLoader);

0 comments on commit bc863bb

Please sign in to comment.