view.js (3326B)
1 /*! 2 * express 3 * Copyright(c) 2009-2013 TJ Holowaychuk 4 * Copyright(c) 2013 Roman Shtylman 5 * Copyright(c) 2014-2015 Douglas Christopher Wilson 6 * MIT Licensed 7 */ 8 9 'use strict'; 10 11 /** 12 * Module dependencies. 13 * @private 14 */ 15 16 var debug = require('debug')('express:view'); 17 var path = require('path'); 18 var fs = require('fs'); 19 20 /** 21 * Module variables. 22 * @private 23 */ 24 25 var dirname = path.dirname; 26 var basename = path.basename; 27 var extname = path.extname; 28 var join = path.join; 29 var resolve = path.resolve; 30 31 /** 32 * Module exports. 33 * @public 34 */ 35 36 module.exports = View; 37 38 /** 39 * Initialize a new `View` with the given `name`. 40 * 41 * Options: 42 * 43 * - `defaultEngine` the default template engine name 44 * - `engines` template engine require() cache 45 * - `root` root path for view lookup 46 * 47 * @param {string} name 48 * @param {object} options 49 * @public 50 */ 51 52 function View(name, options) { 53 var opts = options || {}; 54 55 this.defaultEngine = opts.defaultEngine; 56 this.ext = extname(name); 57 this.name = name; 58 this.root = opts.root; 59 60 if (!this.ext && !this.defaultEngine) { 61 throw new Error('No default engine was specified and no extension was provided.'); 62 } 63 64 var fileName = name; 65 66 if (!this.ext) { 67 // get extension from default engine name 68 this.ext = this.defaultEngine[0] !== '.' 69 ? '.' + this.defaultEngine 70 : this.defaultEngine; 71 72 fileName += this.ext; 73 } 74 75 if (!opts.engines[this.ext]) { 76 // load engine 77 var mod = this.ext.substr(1) 78 debug('require "%s"', mod) 79 80 // default engine export 81 var fn = require(mod).__express 82 83 if (typeof fn !== 'function') { 84 throw new Error('Module "' + mod + '" does not provide a view engine.') 85 } 86 87 opts.engines[this.ext] = fn 88 } 89 90 // store loaded engine 91 this.engine = opts.engines[this.ext]; 92 93 // lookup path 94 this.path = this.lookup(fileName); 95 } 96 97 /** 98 * Lookup view by the given `name` 99 * 100 * @param {string} name 101 * @private 102 */ 103 104 View.prototype.lookup = function lookup(name) { 105 var path; 106 var roots = [].concat(this.root); 107 108 debug('lookup "%s"', name); 109 110 for (var i = 0; i < roots.length && !path; i++) { 111 var root = roots[i]; 112 113 // resolve the path 114 var loc = resolve(root, name); 115 var dir = dirname(loc); 116 var file = basename(loc); 117 118 // resolve the file 119 path = this.resolve(dir, file); 120 } 121 122 return path; 123 }; 124 125 /** 126 * Render with the given options. 127 * 128 * @param {object} options 129 * @param {function} callback 130 * @private 131 */ 132 133 View.prototype.render = function render(options, callback) { 134 debug('render "%s"', this.path); 135 this.engine(this.path, options, callback); 136 }; 137 138 /** 139 * Resolve the file within the given directory. 140 * 141 * @param {string} dir 142 * @param {string} file 143 * @private 144 */ 145 146 View.prototype.resolve = function resolve(dir, file) { 147 var ext = this.ext; 148 149 // <path>.<ext> 150 var path = join(dir, file); 151 var stat = tryStat(path); 152 153 if (stat && stat.isFile()) { 154 return path; 155 } 156 157 // <path>/index.<ext> 158 path = join(dir, basename(file, ext), 'index' + ext); 159 stat = tryStat(path); 160 161 if (stat && stat.isFile()) { 162 return path; 163 } 164 }; 165 166 /** 167 * Return a stat, maybe. 168 * 169 * @param {string} path 170 * @return {fs.Stats} 171 * @private 172 */ 173 174 function tryStat(path) { 175 debug('stat "%s"', path); 176 177 try { 178 return fs.statSync(path); 179 } catch (e) { 180 return undefined; 181 } 182 }