Add support for app.set('views', array)

closes #2320
This commit is contained in:
Douglas Christopher Wilson 2014-10-23 17:18:26 -04:00
parent 0e5f2f84ea
commit 6614352563
8 changed files with 180 additions and 15 deletions

View File

@ -1,6 +1,8 @@
unreleased
==========
* Add support for `app.set('views', array)`
- Views are looked up in sequence in array of directories
* Fix `res.send(status)` to mention `res.sendStatus(status)`
* Use `content-disposition` module for `res.attachment`/`res.download`
- Sends standards-compliant `Content-Disposition` header

View File

@ -513,7 +513,10 @@ app.render = function(name, options, fn){
});
if (!view.path) {
var err = new Error('Failed to lookup view "' + name + '" in views directory "' + view.root + '"');
var dirs = Array.isArray(view.root) && view.root.length > 1
? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"'
: 'directory "' + view.root + '"'
var err = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
err.view = view;
return fn(err);
}

View File

@ -15,7 +15,6 @@ var utils = require('./utils');
var dirname = path.dirname;
var basename = path.basename;
var extname = path.extname;
var exists = fs.existsSync || path.existsSync;
var join = path.join;
var resolve = path.resolve;
@ -53,28 +52,32 @@ function View(name, options) {
}
/**
* Lookup view by the given `path`
* Lookup view by the given `name`
*
* @param {String} path
* @param {String} name
* @return {String}
* @api private
*/
View.prototype.lookup = function lookup(path) {
var ext = this.ext;
var root = this.root;
View.prototype.lookup = function lookup(name) {
var path;
var roots = [].concat(this.root);
debug('lookup "%s"', path);
debug('lookup "%s"', name);
// resolve the path
path = resolve(root, path);
for (var i = 0; i < roots.length && !path; i++) {
var root = roots[i];
// <path>.<engine>
if (exists(path)) return path;
// resolve the path
var loc = resolve(root, name);
var dir = dirname(loc);
var file = basename(loc);
// <path>/index.<engine>
path = join(dirname(path), basename(path, ext), 'index' + ext);
if (exists(path)) return path;
// resolve the file
path = this.resolve(dir, file);
}
return path;
};
/**
@ -89,3 +92,51 @@ View.prototype.render = function render(options, fn) {
debug('render "%s"', this.path);
this.engine(this.path, options, fn);
};
/**
* Resolve the file within the given directory.
*
* @param {string} dir
* @param {string} file
* @private
*/
View.prototype.resolve = function resolve(dir, file) {
var ext = this.ext;
var path;
var stat;
// <path>.<ext>
path = join(dir, file);
stat = tryStat(path);
if (stat && stat.isFile()) {
return path;
}
// <path>/index.<ext>
path = join(dir, basename(file, ext), 'index' + ext);
stat = tryStat(path);
if (stat && stat.isFile()) {
return path;
}
};
/**
* Return a stat, maybe.
*
* @param {string} path
* @return {fs.Stats}
* @private
*/
function tryStat(path) {
debug('stat "%s"', path);
try {
return fs.statSync(path);
} catch (e) {
return undefined;
}
}

View File

@ -131,6 +131,64 @@ describe('app', function(){
})
})
describe('when "views" is given', function(){
it('should lookup the file in the path', function(done){
var app = express();
app.set('views', __dirname + '/fixtures/default_layout');
app.locals.user = { name: 'tobi' };
app.render('user.jade', function(err, str){
if (err) return done(err);
str.should.equal('<p>tobi</p>');
done();
})
})
describe('when array of paths', function(){
it('should lookup the file in the path', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.locals.user = { name: 'tobi' };
app.render('user.jade', function(err, str){
if (err) return done(err);
str.should.equal('<span>tobi</span>');
done();
})
})
it('should lookup in later paths until found', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.locals.name = 'tobi';
app.render('name.jade', function(err, str){
if (err) return done(err);
str.should.equal('<p>tobi</p>');
done();
})
})
it('should error if file does not exist', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.locals.name = 'tobi';
app.render('pet.jade', function(err, str){
err.message.should.equal('Failed to lookup view "pet.jade" in views directories "' + __dirname + '/fixtures/local_layout" or "' + __dirname + '/fixtures/default_layout"');
done();
})
})
})
})
describe('when a "view" constructor is given', function(){
it('should create an instance of it', function(done){
var app = express();

View File

@ -0,0 +1 @@
p= name

View File

@ -0,0 +1 @@
p= user.name

1
test/fixtures/local_layout/user.jade vendored Normal file
View File

@ -0,0 +1 @@
span= user.name

View File

@ -114,6 +114,54 @@ describe('res', function(){
.expect('<p>This is an email</p>', done);
})
})
describe('when "views" is given', function(){
it('should lookup the file in the path', function(done){
var app = express();
app.set('views', __dirname + '/fixtures/default_layout');
app.use(function(req, res){
res.render('user.jade', { user: { name: 'tobi' } });
});
request(app)
.get('/')
.expect('<p>tobi</p>', done);
})
describe('when array of paths', function(){
it('should lookup the file in the path', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.use(function(req, res){
res.render('user.jade', { user: { name: 'tobi' } });
});
request(app)
.get('/')
.expect('<span>tobi</span>', done);
})
it('should lookup in later paths until found', function(done){
var app = express();
var views = [__dirname + '/fixtures/local_layout', __dirname + '/fixtures/default_layout'];
app.set('views', views);
app.use(function(req, res){
res.render('name.jade', { name: 'tobi' });
});
request(app)
.get('/')
.expect('<p>tobi</p>', done);
})
})
})
})
describe('.render(name, option)', function(){