3 * Copyright(c) 2009-2013 TJ Holowaychuk
4 * Copyright(c) 2013 Roman Shtylman
5 * Copyright(c) 2014-2015 Douglas Christopher Wilson
12 * Module dependencies.
16 var debug = require('debug')('express:view');
17 var path = require('path');
18 var fs = require('fs');
25 var dirname = path.dirname;
26 var basename = path.basename;
27 var extname = path.extname;
29 var resolve = path.resolve;
36 module.exports = View;
39 * Initialize a new `View` with the given `name`.
43 * - `defaultEngine` the default template engine name
44 * - `engines` template engine require() cache
45 * - `root` root path for view lookup
47 * @param {string} name
48 * @param {object} options
52 function View(name, options) {
53 var opts = options || {};
55 this.defaultEngine = opts.defaultEngine;
56 this.ext = extname(name);
58 this.root = opts.root;
60 if (!this.ext && !this.defaultEngine) {
61 throw new Error('No default engine was specified and no extension was provided.');
67 // get extension from default engine name
68 this.ext = this.defaultEngine[0] !== '.'
69 ? '.' + this.defaultEngine
75 if (!opts.engines[this.ext]) {
77 var mod = this.ext.slice(1)
78 debug('require "%s"', mod)
80 // default engine export
81 var fn = require(mod).__express
83 if (typeof fn !== 'function') {
84 throw new Error('Module "' + mod + '" does not provide a view engine.')
87 opts.engines[this.ext] = fn
90 // store loaded engine
91 this.engine = opts.engines[this.ext];
94 this.path = this.lookup(fileName);
98 * Lookup view by the given `name`
100 * @param {string} name
104 View.prototype.lookup = function lookup(name) {
106 var roots = [].concat(this.root);
108 debug('lookup "%s"', name);
110 for (var i = 0; i < roots.length && !path; i++) {
114 var loc = resolve(root, name);
115 var dir = dirname(loc);
116 var file = basename(loc);
119 path = this.resolve(dir, file);
126 * Render with the given options.
128 * @param {object} options
129 * @param {function} callback
133 View.prototype.render = function render(options, callback) {
134 debug('render "%s"', this.path);
135 this.engine(this.path, options, callback);
139 * Resolve the file within the given directory.
141 * @param {string} dir
142 * @param {string} file
146 View.prototype.resolve = function resolve(dir, file) {
150 var path = join(dir, file);
151 var stat = tryStat(path);
153 if (stat && stat.isFile()) {
157 // <path>/index.<ext>
158 path = join(dir, basename(file, ext), 'index' + ext);
159 stat = tryStat(path);
161 if (stat && stat.isFile()) {
167 * Return a stat, maybe.
169 * @param {string} path
174 function tryStat(path) {
175 debug('stat "%s"', path);
178 return fs.statSync(path);