/*! * Express - View * Copyright(c) 2010 TJ Holowaychuk * 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 ..//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; };