-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Initial completely untested and non working commit
- Loading branch information
0 parents
commit bc863bb
Showing
3 changed files
with
310 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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": "*" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); |