Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Initial version

  • Loading branch information...
commit 45c8770f20c53c2f767308cdee2270531c4f03a8 0 parents
@niclashoyer authored
12 .gitignore
@@ -0,0 +1,12 @@
+# project specific
+lib/
+docs/
+
+#general swap/backup files
+*.so
+*.log
+*.out
+*~
+*.swp
+*.DS_Store
+*.lock
38 Cakefile
@@ -0,0 +1,38 @@
+###
+Copyright (c) 2012 Niclas Hoyer
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+util = require 'util'
+{exec} = require 'child_process'
+
+task 'build', 'Build src files', ->
+ exec "coffee -bc -o lib src", (err, stdout, stderr) ->
+ util.log err if err
+ util.log "compilation finished"
+
+task 'docs', 'Generate documentation with docco', ->
+ exec "docco src/*.coffee", (err, stdout, stderr) ->
+ util.log err if err
+ util.log "docs generated"
+
18 package.json
@@ -0,0 +1,18 @@
+{
+ "name": "copperhead",
+ "version": "0.1.0",
+ "description": "Connect compatible router middleware that supports content negotiation",
+ "author": {
+ "name": "Niclas Hoyer",
+ "email": "https://github.com/niclashoyer"
+ },
+ "files": [
+ "lib/"
+ ],
+ "main": "lib/Router.js",
+ "repository": {
+ "type": "git",
+ "url": "git://github.com/niclashoyer/copperhead.git"
+ },
+ "licence": "MIT"
+}
81 src/Path.coffee
@@ -0,0 +1,81 @@
+###
+Copyright(c) 2010 Sencha Inc.
+Copyright(c) 2011 TJ Holowaychuk
+Copyright(c) 2011 Niclas Hoyer
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+url = require 'url'
+parse = url.parse
+
+parsePath = (path) ->
+ keys = []
+
+ if path instanceof RegExp
+ return regexp: path, keys: keys
+
+ f = (_, slash, format, key, capture, optional) ->
+ keys.push key
+ slash = slash or ""
+ "" + (if optional then "" else slash) + "(?:" +
+ (if optional then slash else "") + (format or "") +
+ (capture or "([^/]+?)") + ")" + (optional or "")
+
+ path = path
+ .concat("/?")
+ .replace(/\/\(/g, "(?:/")
+ .replace(/(\/)?(\.)?:(\w+)(?:(\(.*?\)))?(\?)?/g, f)
+ .replace(/([\/.])/g, "\\$1")
+ .replace(/\*/g, "(.+)")
+
+ regexp: new RegExp("^" + path + "$", "i"), keys: keys
+
+matchPath = (req, path) ->
+ url = parse req.url
+ pathname = url.pathname
+
+ parsed = parsePath path
+ keys = parsed.keys
+ regexp = parsed.regexp
+
+ captures = regexp.exec pathname
+
+ if captures?
+
+ captures = captures.slice 1
+
+ if not req.params?
+ req.params = []
+
+ for capture, i in captures
+ key = keys[i]
+ if key?
+ req.params[key] = capture
+ else
+ req.params.push capture
+
+ true
+ else
+ false
+
+module.exports = matchPath
181 src/Router.coffee
@@ -0,0 +1,181 @@
+###
+Copyright (c) 2011 Niclas Hoyer
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
+###
+
+Onion = require 'onion'
+utils = require 'onion.utils'
+filter = utils.filter
+
+accept = require 'http-accept'
+matchPath = require './Path'
+
+class Wait
+ constructor ->
+
+router = new Onion
+
+methods = [
+ # HTTP 1.1
+ 'get',
+ 'post',
+ 'put',
+ 'delete',
+ 'options',
+ 'patch',
+ # WebDAV
+ 'connect',
+ 'trace',
+ 'copy',
+ 'lock',
+ 'mkcol',
+ 'move',
+ 'propfind',
+ 'proppatch',
+ 'unlock',
+ 'report',
+ 'mkactivity',
+ 'checkout',
+ 'merge'
+]
+
+endResponse = (res, mimetype, body) ->
+ res.setHeader 'Content-Type', mimetype.type+'/'+mimetype.subtype
+ if mimetype.subtype is 'json'
+ body = JSON.stringify body
+ res.end body
+
+routesMiddleware = (req, res, next) ->
+ callback = =>
+ if req.accept.types instanceof Array
+ mediaranges = (type.mediarange for type in req.accept.types)
+ router.peelFor mediaranges, req, res, next
+ else
+ router.peelFor '*/*'
+
+ router.innermost = ->
+ next()
+
+ if req.accept is undefined
+ accept req, res, callback
+ else
+ callback()
+
+route = (method, path, mimetype, middleware) ->
+ method = method.toUpperCase()
+
+ if arguments.length is 2
+ middleware = path
+ path = '/'
+ mimetype = undefined
+
+ else if arguments.length is 3
+ middleware = mimetype
+
+ # If the first character of path is not `/` assume that a mimetype was
+ # given.
+ if typeof path is 'string'
+ if path[0] isnt '/'
+ mimetype = path
+ path = '/'
+
+ # Otherwise interpret `path` as a path and set mimetype `undefined`.
+ else
+ mimetype = undefined
+ else
+ path = '/'
+
+ match = (req) ->
+ if req.method is method
+ matchPath req, path
+ else
+ false
+
+ if typeof mimetype is 'string'
+ mediarange = mimetype.split '/'
+ type = mediarange[0]
+ subtype = mediarange[1]
+ else
+ type = '*'
+ subtype = '*'
+
+ # if there is something returned, end the response,
+ # otherwise call next middleware.
+ handleMiddleware = (req, res, next) ->
+ context = {}
+
+ if req.params?
+ for param, value of req.params
+ context[param] = value
+
+ wait = new Wait()
+ context.wait = wait
+
+ body = middleware.call context, req, res, next
+
+ if body?
+ if body is wait
+ return
+ else if mimetype?
+ mediarange = mimetype.split '/'
+ resptype =
+ type: mediarange[0]
+ subtype: mediarange[1]
+ endResponse res, resptype, body
+
+ else if typeof body is 'string'
+ endResponse res, {type: 'text', subtype: 'plain'}, body
+
+ else if typeof body is 'object'
+ endResponse res, {type: 'application', subtype: 'json'}, body
+
+ else
+ next()
+
+ stackForMime = (mimetype) =>
+ router.stack mimetype, filter match, handleMiddleware
+
+ stackForMime '*/*'
+
+ if type isnt '*'
+ stackForMime type+'/*'
+
+ if subtype isnt '*'
+ stackForMime mimetype
+
+ return
+
+generateRouteMethods = ->
+ for method in methods
+ do (method) =>
+ module.exports[method] = (args...) =>
+ route.apply this, [method].concat args
+ return
+
+# Export middleware as default.
+module.exports = routesMiddleware
+# Additionally export all HTTP methods and
+generateRouteMethods()
+# export a general route methods for custom routes.
+module.exports.route = route
+
22 test.coffee
@@ -0,0 +1,22 @@
+connect = require 'connect'
+http = require 'http'
+
+router = require 'copperhead'
+
+app = connect()
+app.use connect.logger 'dev'
+app.use router
+
+router.get '/foo', ->
+ 'bar!'
+
+router.get ->
+ 'Hello World!'
+
+router.get 'application/json', ->
+ hello: "World!"
+
+router.get 'text/html', ->
+ '<!DOCTYPE html><html><head><title>Hello World!</title></head><body></body>Hello!</html>'
+
+app.listen 3000
Please sign in to comment.
Something went wrong with that request. Please try again.