211 lines
4.1 KiB
JavaScript
211 lines
4.1 KiB
JavaScript
|
|
||
|
/*!
|
||
|
* Express - View
|
||
|
* Copyright(c) 2010 TJ Holowaychuk <tj@vision-media.ca>
|
||
|
* MIT Licensed
|
||
|
*/
|
||
|
|
||
|
/**
|
||
|
* Module dependencies.
|
||
|
*/
|
||
|
|
||
|
var path = require('path')
|
||
|
, utils = require('../utils')
|
||
|
, extname = path.extname
|
||
|
, dirname = path.dirname
|
||
|
, basename = path.basename
|
||
|
, fs = require('fs')
|
||
|
, stat = fs.statSync;
|
||
|
|
||
|
/**
|
||
|
* Expose `View`.
|
||
|
*/
|
||
|
|
||
|
exports = module.exports = View;
|
||
|
|
||
|
/**
|
||
|
* Require cache.
|
||
|
*/
|
||
|
|
||
|
var cache = {};
|
||
|
|
||
|
/**
|
||
|
* Initialize a new `View` with the given `view` path and `options`.
|
||
|
*
|
||
|
* @param {String} view
|
||
|
* @param {Object} options
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
function View(view, options) {
|
||
|
options = options || {};
|
||
|
this.view = view;
|
||
|
this.root = options.root;
|
||
|
this.relative = false !== options.relative;
|
||
|
this.defaultEngine = options.defaultEngine;
|
||
|
this.parent = options.parentView;
|
||
|
this.basename = basename(view);
|
||
|
this.engine = this.resolveEngine();
|
||
|
this.extension = '.' + this.engine;
|
||
|
this.name = this.basename.replace(this.extension, '');
|
||
|
this.path = this.resolvePath();
|
||
|
this.dirname = dirname(this.path);
|
||
|
if (options.attempts) {
|
||
|
if (!~options.attempts.indexOf(this.path))
|
||
|
options.attempts.push(this.path);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Check if the view path exists.
|
||
|
*
|
||
|
* @return {Boolean}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('exists', function(){
|
||
|
try {
|
||
|
stat(this.path);
|
||
|
return true;
|
||
|
} catch (err) {
|
||
|
return false;
|
||
|
}
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Resolve view engine.
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
View.prototype.resolveEngine = function(){
|
||
|
// Explicit
|
||
|
if (~this.basename.indexOf('.')) return extname(this.basename).substr(1);
|
||
|
// Inherit from parent
|
||
|
if (this.parent) return this.parent.engine;
|
||
|
// Default
|
||
|
return this.defaultEngine;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Resolve view path.
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api private
|
||
|
*/
|
||
|
|
||
|
View.prototype.resolvePath = function(){
|
||
|
var path = this.view;
|
||
|
// Implicit engine
|
||
|
if (!~this.basename.indexOf('.')) path += this.extension;
|
||
|
// Absolute
|
||
|
if (utils.isAbsolute(path)) return path;
|
||
|
// Relative to parent
|
||
|
if (this.relative && this.parent) return this.parent.dirname + '/' + path;
|
||
|
// Relative to root
|
||
|
return this.root
|
||
|
? this.root + '/' + path
|
||
|
: path;
|
||
|
};
|
||
|
|
||
|
/**
|
||
|
* Get view contents. This is a one-time hit, so we
|
||
|
* can afford to be sync.
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('contents', function(){
|
||
|
return fs.readFileSync(this.path, 'utf8');
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Get template engine api, cache exports to reduce
|
||
|
* require() calls.
|
||
|
*
|
||
|
* @return {Object}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('templateEngine', function(){
|
||
|
var ext = this.extension;
|
||
|
return cache[ext] || (cache[ext] = require(this.engine));
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Return root path alternative.
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('rootPath', function(){
|
||
|
this.relative = false;
|
||
|
return this.resolvePath();
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Return index path alternative.
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('indexPath', function(){
|
||
|
return this.dirname
|
||
|
+ '/' + this.basename.replace(this.extension, '')
|
||
|
+ '/index' + this.extension;
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Return ../<name>/index path alternative.
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('upIndexPath', function(){
|
||
|
return this.dirname + '/../' + this.name + '/index' + this.extension;
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Return _ prefix path alternative
|
||
|
*
|
||
|
* @return {String}
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
View.prototype.__defineGetter__('prefixPath', function(){
|
||
|
return this.dirname + '/_' + this.basename;
|
||
|
});
|
||
|
|
||
|
/**
|
||
|
* Register the given template engine `exports`
|
||
|
* as `ext`. For example we may wish to map ".html"
|
||
|
* files to jade:
|
||
|
*
|
||
|
* app.register('.html', require('jade'));
|
||
|
*
|
||
|
* or
|
||
|
*
|
||
|
* app.register('html', require('jade'));
|
||
|
*
|
||
|
* This is also useful for libraries that may not
|
||
|
* match extensions correctly. For example my haml.js
|
||
|
* library is installed from npm as "hamljs" so instead
|
||
|
* of layout.hamljs, we can register the engine as ".haml":
|
||
|
*
|
||
|
* app.register('.haml', require('haml-js'));
|
||
|
*
|
||
|
* @param {String} ext
|
||
|
* @param {Object} obj
|
||
|
* @api public
|
||
|
*/
|
||
|
|
||
|
exports.register = function(ext, exports) {
|
||
|
if ('.' != ext[0]) ext = '.' + ext;
|
||
|
cache[ext] = exports;
|
||
|
};
|