Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

View engines system

  • Loading branch information...
commit 6dc741fcbe25b73cd5b9431ce811d6e449e34145 1 parent cc4d7c6
@eldargab authored
View
19 lib/engines/index.js
@@ -0,0 +1,19 @@
+var basename = require('path').basename
+var extname = require('path').extname
+
+var engines = module.exports = function (ext) {
+ var factory = engines.extensions[ext] || engines[ext]
+ return factory()
+}
+
+require('fs').readdirSync(__dirname).forEach(function (item) {
+ if (~['index.js', '.', '..'].indexOf(item)) return
+ item = item.replace(/\.\w+$/, '')
+ engines[item] = require('./' + item)
+})
+
+engines.extensions = {
+ '.jade': engines.jade,
+ '.md': engines.markdown,
+ '.less': engines.less
+}
View
9 lib/engines/jade.js
@@ -0,0 +1,9 @@
+module.exports = function () {
+ var renderFile = require('jade').renderFile
+ return {
+ renderFile: function (file, cb) {
+ renderFile(file, this, cb)
+ },
+ contentType: 'text/html'
+ }
+}
View
31 lib/engines/less.js
@@ -0,0 +1,31 @@
+var fs = require('fs')
+var Parser
+
+module.exports = function () {
+ Parser = require('less').Parser
+ return {
+ contentType: 'text/css',
+ renderFile: render
+ }
+}
+
+function render (file, cb) {
+ var parser = new Parser({
+ filename: file,
+ paths: this.root ? [this.root] : null
+ })
+
+ fs.readFile(file, 'utf8', function (error, string) {
+ if (error) return cb(error)
+ parser.parse(string, function (err, tree) {
+ if (err) return cb(err)
+ try {
+ var out = tree.toCSS()
+ }
+ catch (e) {
+ return cb(e)
+ }
+ cb(null, out)
+ })
+ })
+}
View
15 lib/engines/markdown.js
@@ -0,0 +1,15 @@
+module.exports = function () {
+ var Markdown = require('markdown').markdown
+
+ return {
+ contentType: 'text/html',
+
+ render: function (string, cb) {
+ var dialect = this.dialect
+ if (typeof dialect == 'string')
+ dialect = Markdown.dialects[dialect]
+ var out = Markdown.toHTML(string, dialect)
+ cb(null, out)
+ }
+ }
+}
View
9 lib/http-error.js
@@ -1,9 +0,0 @@
-var STATUS_CODES = require('http').STATUS_CODES
-
-module.exports = function (code, text) {
- var msg = STATUS_CODES[code]
- if (text) msg = msg + '. ' + text
- var err = new Error(msg)
- err.status = code
- return err
-}
View
36 lib/index.js
@@ -1,7 +1,37 @@
var Middleware = require('./middleware')
-var PathHandler = require('./path-handler')
+var Lookup = require('./path-lookup')
+var Render = require('./render')
+var engines = require('./engines')
+var mix = require('./util').mix
module.exports = function (opts) {
opts = opts || {}
- return Middleware(opts.root, PathHandler(opts))
-}
+ opts.root = opts.root || process.cwd()
+ opts.pathHandler = opts.pathHandler || Render(function (ext) {
+ return opts.engine
+ ? opts.engine(ext)
+ : engine(ext, opts)
+ })
+ return Middleware(opts.root, Lookup(function (path, req, res, next) {
+ opts.pathHandler(path, req, res, next)
+ }))
+}
+
+function engine (ext, opts) {
+ var cach = engine.cach
+ var eng = cach[ext]
+ if (eng === undefined) {
+ eng = cach[ext] = opts[ext] || engines(ext) || null
+ if (eng) {
+ var extName = ext.substring(1)
+ mix(eng, {root: opts.root}, opts['*'], opts[extName])
+ }
+ }
+ return eng ? Object.create(eng) : null
+}
+
+engine.cach = {}
+
+module.exports.engine = engine
+module.exports.engines = engines
+module.exports.Render = Render
View
4 lib/middleware.js
@@ -1,6 +1,6 @@
var parseUrl = require('url').parse
var PATH = require('path')
-var error = require('./http-error')
+var error = require('./util').httpError
module.exports = function (root, handle) {
if (typeof root == 'function') {
@@ -10,6 +10,8 @@ module.exports = function (root, handle) {
root = root || process.cwd()
return function fileHandler (req, res, next) {
+ if (req.method != 'GET' && req.method != 'HEAD') return next()
+
var path = decodeUri(parseUrl(req.url).pathname)
if (path instanceof URIError) return next(error(400))
View
27 lib/path-handler.js → lib/path-lookup.js
@@ -1,27 +1,10 @@
var fs = require('fs')
var PATH = require('path')
-module.exports = function (opts) {
+module.exports = function (onfile, opts) {
opts = opts || {}
-
- var fsStat = opts.fsStat = opts.fsStat || fs.stat
- var readdir = opts.fsDir = opts.fsDir || fs.readdir
- var exts = opts.exts || {}
-
- function Options (req, res, next) {
- this.req = req
- this.res = res
- this.next = next
- }
-
- Options.prototype = opts
-
- function handleFile (file, req, res, next) {
- var ext = PATH.extname(file)
- var handler = exts[ext] || exts['*']
- if (!handler) return next()
- handler(file, new Options(req, res, next))
- }
+ var fsStat = opts.fsStat || fs.stat
+ var readdir = opts.fsDir || fs.readdir
function lookup (path, cb) {
var dir = PATH.dirname(path)
@@ -45,12 +28,12 @@ module.exports = function (opts) {
if (error) return next()
if (files.length == 0) return next()
if (files.length > 1) return next() // TODO: Respond with 300?
- handleFile(files[0], req, res, next)
+ onfile(files[0], req, res, next)
})
} else if (stat.isDirectory()) {
handle(PATH.join(path, 'index'), req, res, next)
} else {
- handleFile(path, req, res, next)
+ onfile(path, req, res, next)
}
})
}
View
27 lib/render.js
@@ -0,0 +1,27 @@
+var onerror = require('./util').nextOnENOENT
+var extname = require('path').extname
+var fs = require('fs')
+
+module.exports = function (Engine) {
+ return function render (file, req, res, next) {
+ var engine = Engine(extname(file))
+ if (engine && engine.renderFile) {
+ engine.renderFile(file, function (error, out) {
+ if (error) return onerror(error, next)
+ res.set('Content-Type', engine.contentType)
+ res.send(out)
+ })
+ } else if (engine && engine.render) {
+ fs.readFile(file, 'utf8', function (error, string) {
+ if (error) return onerror(error, next)
+ engine.render(string, function (error, out) {
+ if (error) return next(error)
+ res.set('Content-Type', engine.contentType)
+ res.send(out)
+ })
+ })
+ } else {
+ res.sendfile(file)
+ }
+ }
+}
View
23 lib/util.js
@@ -0,0 +1,23 @@
+var STATUS_CODES = require('http').STATUS_CODES
+
+exports.httpError = function (code, text) {
+ var msg = STATUS_CODES[code]
+ if (text) msg = msg + '. ' + text
+ var err = new Error(msg)
+ err.status = code
+ return err
+}
+
+exports.nextOnENOENT = function (error, next) {
+ error.code == 'ENOENT' ? next() : next(error)
+}
+
+exports.mix = function (t, var_src) {
+ for (var i = 1; i < arguments.length; i++) {
+ var src = arguments[i]
+ for (var key in src) {
+ t[key] = src[key]
+ }
+ }
+ return t
+}
View
6 package.json
@@ -4,5 +4,9 @@
"description": "Serve your jades, markdowns, lesses like static files",
"version": "0.0.0",
"main": "lib/index",
- "devDependencies": {}
+ "devDependencies": {
+ "less": "*",
+ "jade": "*",
+ "markdown": "*"
+ }
}
View
1  test/fixtures/jade/hello.jade
@@ -0,0 +1 @@
+h1 #{hello} #{world}
View
1  test/fixtures/less/index.less
@@ -0,0 +1 @@
+@import "main";
View
5 test/fixtures/less/main.less
@@ -0,0 +1,5 @@
+@color: #4D926F;
+
+#header {
+ color: @color;
+}
View
1  test/fixtures/markdown/hello.md
@@ -0,0 +1 @@
+#Hello
View
37 test/integration.js
@@ -0,0 +1,37 @@
+var express = require('express')
+var Request = require('./support/http').Request
+var Views = require('../lib')
+
+describe('Integration tests', function () {
+ beforeEach(function () {
+ this.app = express()
+ this.req = new Request(this.app)
+ })
+
+ function testEngine (name, options, fn) {
+ if (typeof options == 'function') {
+ fn = options
+ options = {}
+ }
+ options.root = __dirname + '/fixtures/' + name.toLowerCase()
+ it(name, function (done) {
+ this.app.use(Views(options))
+ fn(this.req, done)
+ })
+ }
+
+ testEngine('Less', function (req, done) {
+ req.get('/').expect('text/css', /header/, done)
+ })
+
+ testEngine('Markdown', function (req, done) {
+ req.get('/hello').expect('text/html', /<h1>Hello<\/h1>/, done)
+ })
+
+ testEngine('Jade', {
+ '*': {hello: 'hello'},
+ 'jade': {world: 'world'}
+ }, function (req, done) {
+ req.get('/hello').expect('text/html', /hello world/, done)
+ })
+})
View
4 test/middleware.js
@@ -9,7 +9,7 @@ describe('Middleware', function () {
handler = sinon.spy()
m = Middleware('root', handler)
next = Next()
- req = {}
+ req = { method: 'GET'}
res = {}
})
@@ -18,7 +18,7 @@ describe('Middleware', function () {
m(req, res, next)
}
- it('Should pass path, req, res and next to handler', function () {
+ it('Should pass path, req, res and next to the handler', function () {
test('/article')
handler.calledWithExactly('root/article', req, res, next).should.be.true
})
View
60 test/path-handler.js
@@ -1,9 +1,9 @@
var sinon = require('sinon')
var Fs = require('./support/fake-fs')
var Next = require('./support/next')
-var Handler = require('../lib/path-handler')
+var Lookup = require('../lib/path-lookup')
-function ExtHandler () {
+function Handler () {
var spy = sinon.spy()
spy.handles = function (file) {
@@ -11,34 +11,27 @@ function ExtHandler () {
this.calledWith(file).should.be.true
}
- spy.options = function () {
- this.calledOnce.should.be.true
- return this.firstCall.args[1]
+ spy.notCalled = function () {
+ this.called.should.be.false
}
return spy
}
describe('Path handler', function () {
- var h, next, exts, fs, options, req, res
+ var lookup, next, fs, req, res, h, options
beforeEach(function () {
- exts = {
- '.jade': ExtHandler(),
- '.md': ExtHandler()
- }
fs = new Fs
options = {
- exts: exts,
fsStat: function (p, cb) { fs.stat(p, cb) },
fsDir: function (p, cb) { fs.readdir(p, cb) },
- setting: 'setting'
}
- h = Handler(options)
+ h = Handler()
+ lookup = Lookup(h, options)
next = Next()
req = {}
res = {}
-
fs.paths('root', [
'article.md',
'doc.jade',
@@ -48,58 +41,42 @@ describe('Path handler', function () {
})
function test (path) {
- h(path, req, res, next)
+ lookup(path, req, res, next)
}
- it('Should be able to determine file extension and handler', function () {
+ it('Should be able to lookup file extension', function () {
test('root/article')
next.notCalled()
- exts['.md'].handles('root/article.md')
+ h.handles('root/article.md')
})
it('Should serve exact paths', function () {
test('root/article.md')
next.notCalled()
- exts['.md'].handles('root/article.md')
- })
-
- it('Should use `*` as a default handler', function () {
- exts['*'] = ExtHandler()
- test('root/index.html')
- next.notCalled()
- exts['*'].handles('root/index.html')
+ h.handles('root/article.md')
})
- it('Should pass filename, req, res, next and passed settings to handler', function () {
+ it('Should pass filename, req, res, next to the handler', function () {
test('root/article')
- exts['.md'].handles('root/article.md')
- var opts = exts['.md'].options()
- opts.req.should.equal(req)
- opts.res.should.equal(res)
- opts.next.should.equal(next)
- opts.setting.should.equal(options.setting)
-
+ h.calledWithExactly('root/article.md', req, res, next)
})
- it('Should support index files in dirs', function () {
+ it('Should support index files for dirs', function () {
test('root/dir')
next.notCalled()
- exts['.jade'].handles('root/dir/index.jade')
+ h.handles('root/dir/index.jade')
})
it('Should pass control to next middleware on non-existent path', function () {
test('non-existent/path')
next.nextMiddleware()
+ h.notCalled()
})
it('Should pass control to next middleware when no appropriate file found', function () {
test('root/hello')
next.nextMiddleware()
- })
-
- it('Should pass control to next middleware when handler not found', function () {
- test('root/index.html')
- next.nextMiddleware()
+ h.notCalled()
})
it('Should pass fs.stat errors to next middleware', function () {
@@ -107,8 +84,8 @@ describe('Path handler', function () {
var error = new Error
stat.withArgs('error').yields(error)
test('error')
- debugger
next.calledWithExactly(error).should.be.true
+ h.notCalled()
})
it('Should pass fs.readdir errors to next middleware', function () {
@@ -118,5 +95,6 @@ describe('Path handler', function () {
readdir.withArgs('readdir/error').yields(error)
test('readdir/error')
next.calledWithExactly(error).should.be.true
+ h.notCalled()
})
})
View
75 test/support/http.js
@@ -0,0 +1,75 @@
+var http = require('http')
+
+module.exports.Request = Request
+
+function Request (app) {
+ var self = this
+ this.data = []
+ this.header = {}
+ this.app = app
+ if (!this.server) {
+ this.server = http.Server(app)
+ this.server.listen(0, function(){
+ self.addr = self.server.address()
+ self.listening = true
+ })
+ }
+}
+
+Request.prototype.get = function (path) {
+ return this.request('GET', path)
+}
+
+Request.prototype.request = function (method, path) {
+ this.method = method
+ this.path = path
+ return this
+}
+
+Request.prototype.set = function(field, val){
+ this.header[field] = val
+ return this
+}
+
+Request.prototype.expect = function(contentType, body, fn){
+ this.end(function(res){
+ res.headers['content-type'].should.equal(contentType)
+ if (body instanceof RegExp) {
+ res.body.should.match(body)
+ } else {
+ res.body.should.equal(body)
+ }
+ fn()
+ })
+}
+
+Request.prototype.end = function(fn){
+ var self = this
+
+ if (this.listening) {
+ var req = http.request({
+ method: this.method,
+ port: this.addr.port,
+ host: this.addr.address,
+ path: this.path,
+ headers: this.header
+ })
+
+ req.on('response', function(res){
+ var buf = ''
+ res.setEncoding('utf8')
+ res.on('data', function(chunk){ buf += chunk })
+ res.on('end', function(){
+ res.body = buf
+ fn(res)
+ })
+ })
+
+ req.end()
+ } else {
+ this.server.on('listening', function(){
+ self.end(fn)
+ })
+ }
+ return this
+}
Please sign in to comment.
Something went wrong with that request. Please try again.