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
|
}
|