Skip to content
This repository was archived by the owner on Jan 25, 2020. It is now read-only.

Commit d16bed6

Browse files
committed
Rearrange what is exported by what
This gets ready to remove the middleware part into a kraken-specific package.
1 parent b888cef commit d16bed6

File tree

6 files changed

+302
-314
lines changed

6 files changed

+302
-314
lines changed

index.js

Lines changed: 193 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,198 @@
1515
│ See the License for the specific language governing permissions and │
1616
│ limitations under the License. │
1717
\*───────────────────────────────────────────────────────────────────────────*/
18+
"use strict";
19+
var path = require('path');
20+
var debug = require('debuglog')('engine-munger');
21+
var fs = require('fs');
22+
var permutron = require('permutron');
23+
var oldView = require('express/lib/view');
24+
var karka = require('karka');
25+
var aproba = require('aproba');
26+
var bcp47 = require('bcp47');
27+
var bcp47stringify = require('bcp47-stringify');
1828

19-
'use strict';
29+
/**
30+
* Make a View class that uses our configuration, set far in advance of
31+
* instantiation because Express passes very little to the actual constructor.
32+
*/
33+
function makeViewClass(config) {
34+
aproba('O', arguments);
2035

21-
exports.setupViewClass = require('./lib/expressView');
36+
var conf = normalizeConfigs(config);
37+
38+
var proto = Object.create(oldView.prototype);
39+
40+
// Unfortunately, since we don't know the actual file to resolve until
41+
// we get request context (in `render`), we can't say whether it exists or not.
42+
//
43+
// Express checks that this is truthy to see if it should return an error or
44+
// run the render, so we hard code it to true.
45+
proto.path = true;
46+
47+
proto.lookup = function lookup(name, options, cb) {
48+
if (arguments.length === 1) {
49+
// This is the unoverriden constructor calling us. Ignore the call.
50+
return true;
51+
}
52+
53+
var ext = path.extname(name);
54+
55+
if (conf[ext] && conf[ext].specialization) {
56+
var nameNoExt = name.slice(0, -ext.length);
57+
var newName = conf[ext].specialization.resolve(nameNoExt, options) + ext;
58+
debug("specialization mapped '%s' to '%s'", name, newName);
59+
name = newName;
60+
}
61+
62+
var search = [];
63+
search.push([].concat(conf[ext] && conf[ext].root ? conf[ext].root : this.root));
64+
65+
if (conf[ext] && conf[ext].i18n) {
66+
var i18n = conf[ext].i18n;
67+
var locales = [];
68+
if (options.locale) {
69+
locales.push(i18n.formatPath(typeof options.locale === 'object' ? options.locale : bcp47.parse(options.locale.replace(/_/g, '-'))));
70+
}
71+
if (i18n.fallback) {
72+
locales.push(i18n.formatPath(i18n.fallback));
73+
}
74+
debug("trying locales %j", locales);
75+
search.push(locales);
76+
}
77+
78+
search.push([name, path.join(path.basename(name), 'index' + ext)]);
79+
80+
debug('lookup "%s"', name);
81+
82+
permutron.raw(search, function (candidate, next) {
83+
var resolved = path.resolve.apply(null, candidate);
84+
limitStat(resolved, function (err, stat) {
85+
if (!err && stat.isFile()) {
86+
debug('found "%s"', resolved);
87+
cb(null, resolved);
88+
} else if ((!err && stat.isDirectory()) || (err && err.code === 'ENOENT')) {
89+
next();
90+
} else {
91+
cb(err);
92+
}
93+
});
94+
}, cb);
95+
};
96+
97+
/**
98+
* Render with the given `options` and callback `fn(err, str)`.
99+
*
100+
* @param {Object} options
101+
* @param {Function} fn
102+
* @api private
103+
*/
104+
proto.render = function render(options, fn) {
105+
aproba('OF', arguments);
106+
var view = this;
107+
view.lookupMain(options, function (err) {
108+
if (err) {
109+
fn(err);
110+
} else {
111+
debug('render "%s"', view.path);
112+
view.engine(view.path, options, fn);
113+
}
114+
});
115+
};
116+
117+
/** Resolve the main template for this view
118+
*
119+
* @param {function} cb
120+
* @private
121+
*/
122+
proto.lookupMain = function lookupMain(options, cb) {
123+
if (this.path && this.path !== true) {
124+
return cb();
125+
}
126+
var view = this;
127+
var name = path.extname(this.name) === this.ext ? this.name : this.name + this.ext;
128+
this.lookup(name, options, function (err, path) {
129+
if (err) {
130+
return cb(err);
131+
} else if (!path) {
132+
var dirs = Array.isArray(view.root) && view.root.length > 1 ? 'directories "' + view.root.slice(0, -1).join('", "') + '" or "' + view.root[view.root.length - 1] + '"' : 'directory "' + view.root + '"';
133+
var viewError = new Error('Failed to lookup view "' + name + '" in views ' + dirs);
134+
viewError.view = view;
135+
return cb(viewError);
136+
} else {
137+
view.path = path;
138+
cb();
139+
}
140+
});
141+
};
142+
143+
function View(name, options) {
144+
oldView.call(this, name, options);
145+
}
146+
147+
View.prototype = proto;
148+
View.prototype.constructor = View;
149+
return View;
150+
}
151+
152+
module.exports = makeViewClass;
153+
154+
/**
155+
* an fs.stat call that limits the number of outstanding requests to 10.
156+
*
157+
* @param {String} path
158+
* @param {Function} cb
159+
*/
160+
var pendingStats = [];
161+
var numPendingStats = 0;
162+
163+
function limitStat(path, cb) {
164+
debug('stat "%s"', path);
165+
if (++numPendingStats > 10) {
166+
pendingStats.push([path, cb]);
167+
} else {
168+
fs.stat(path, dequeue(cb));
169+
}
170+
171+
function dequeue(cb) {
172+
return function (err, stat) {
173+
cb(err, stat);
174+
var next = pendingStats.shift();
175+
if (next) {
176+
fs.stat(next[0], dequeue(next[1]));
177+
} else {
178+
numPendingStats--;
179+
}
180+
};
181+
}
182+
}
183+
184+
function normalizeConfigs(config) {
185+
var out = {};
186+
for (var ext in config) {
187+
if (ext[0] === '.') {
188+
out[ext] = normalizeConfig(config[ext]);
189+
} else {
190+
out['.' + ext] = normalizeConfig(config[ext]);
191+
}
192+
}
193+
194+
return out;
195+
}
196+
197+
function normalizeConfig(config) {
198+
var out = {};
199+
if (config.i18n) {
200+
out.i18n = {
201+
fallback: config.i18n.fallback && bcp47.parse(config.i18n.fallback.replace(/_/g, '-')),
202+
formatPath: config.i18n.formatPath || bcp47stringify,
203+
contentPath: config.i18n.contentPath
204+
};
205+
}
206+
207+
if (config.specialization) {
208+
out.specialization = karka.create(config.specialization);
209+
}
210+
211+
return out;
212+
}

0 commit comments

Comments
 (0)