UNPKG

8.77 kBJavaScriptView Raw
1var _ = require('underscore'),
2 EventEmitter = require('events').EventEmitter,
3 fs = require('fs'),
4 path = require('path'),
5 handlebars = require('handlebars'),
6 resources = require('./util/resources');
7
8const EMFILE_RETRY = 250;
9
10var fileCache = {};
11
12exports = module.exports = new EventEmitter();
13
14function cacheRead(filePath, exec, callback) {
15 filePath = filePath && path.normalize(filePath);
16 filePath = exports.resolvePath(filePath);
17
18 var cache = fileCache[filePath];
19 if (cache) {
20 if (cache.data) {
21 callback(undefined, cache);
22 } else {
23 cache.pending.push(callback);
24 }
25 return;
26 }
27
28 cache = fileCache[filePath] = {
29 pending: [callback],
30 artifacts: {}
31 };
32
33 exec(filePath, function _callback(err, data) {
34 if (err && err.code === 'EMFILE') {
35 setTimeout(exec.bind(this, filePath, _callback), EMFILE_RETRY);
36 } else {
37 if (err) {
38 delete fileCache[filePath];
39 }
40
41 cache.data = data;
42 cache.pending.forEach(function(callback) {
43 callback(err, cache);
44 });
45 exports.emit('cache:set', filePath);
46 }
47 });
48}
49
50exports.resetCache = function(filePath) {
51 filePath = filePath && path.normalize(filePath);
52 exports.emit('cache:reset', filePath);
53
54 if (filePath) {
55 filePath = exports.resolvePath(filePath);
56 delete fileCache[filePath];
57 } else {
58 fileCache = {};
59 }
60};
61
62var lookupPath;
63exports.resolvePath = function(pathName) {
64 // Poormans path.resolve. We aren't able to use the bundled path.resolve due to
65 // it throwing sync EMFILE errors without a type to key on.
66 if (lookupPath
67 && (pathName[0] !== '/' && pathName.indexOf(':/') === -1 && pathName.indexOf(':\\') === -1)
68 && pathName.indexOf(lookupPath) !== 0) {
69 return lookupPath + pathName;
70 } else {
71 return pathName;
72 }
73};
74exports.makeRelative = function(pathName) {
75 if (pathName.indexOf(lookupPath) === 0) {
76 return pathName.substring(lookupPath.length);
77 } else {
78 return pathName;
79 }
80};
81
82exports.lookupPath = function(pathName) {
83 if (pathName !== undefined) {
84 lookupPath = pathName;
85 if (lookupPath && !/\/$/.test(lookupPath)) {
86 lookupPath += '/';
87 }
88 }
89 return lookupPath;
90};
91
92exports.stat = function(file, callback) {
93 fs.stat(file, function(err, stat) {
94 if (err && err.code === 'EMFILE') {
95 setTimeout(exports.stat.bind(exports, file, callback), EMFILE_RETRY);
96 } else {
97 callback(err, stat);
98 }
99 });
100};
101
102exports.readFileSync = function(file) {
103 return fs.readFileSync(exports.resolvePath(file));
104};
105exports.readFile = function(file, callback) {
106 cacheRead(file, fs.readFile.bind(fs), function(err, cache) {
107 callback(err, cache && cache.data);
108 });
109};
110exports.readFileArtifact = function(file, name, callback) {
111 cacheRead(file, fs.readFile.bind(fs), function(err, cache) {
112 var artifacts = cache.artifacts;
113 callback(err, {data: cache.data, artifact: artifacts[name]});
114 });
115};
116exports.setFileArtifact = function(path, name, artifact) {
117 path = exports.resolvePath(path);
118
119 var cache = fileCache[path];
120 if (cache) {
121 cache.artifacts[name] = artifact;
122 }
123};
124
125exports.readdir = function(dir, callback) {
126 cacheRead(dir, fs.readdir.bind(fs), function(err, cache) {
127 callback(err, cache && cache.data);
128 });
129};
130
131exports.ensureDirs = function(pathname, callback) {
132 var dirname = path.dirname(pathname);
133 exports.stat(dirname, function(err) {
134 if (err && err.code === 'ENOENT') {
135 // If we don't exist, check to see if our parent exists before trying to create ourselves
136 exports.ensureDirs(dirname, function() {
137 fs.mkdir(dirname, parseInt('0755', 8), function _callback(err) {
138 if (err && err.code === 'EMFILE') {
139 setTimeout(fs.mkdir.bind(fs, dirname, parseInt('0755', 8), _callback), EMFILE_RETRY);
140 } else {
141 // Off to the races... and we lost.
142 callback(err && err.code === 'EEXIST' ? undefined : err);
143 }
144 });
145 });
146 } else {
147 callback();
148 }
149 });
150};
151
152exports.writeFile = function(file, data, callback) {
153 exports.resetCache(file);
154
155 exports.ensureDirs(file, function(err) {
156 if (err) {
157 return callback(err);
158 }
159
160 fs.writeFile(file, data, 'utf8', function _callback(err) {
161 if (err && err.code === 'EMFILE') {
162 setTimeout(fs.writeFile.bind(fs, file, data, 'utf8', _callback), EMFILE_RETRY);
163 } else {
164 callback(err);
165 }
166 });
167 });
168};
169
170/**
171 * Takes a given input and returns the files that are represented.
172 *
173 * pathname may be:
174 * a resource object
175 * a path on the file system
176 * an array of resources
177 */
178exports.fileList = function(pathname, extension, callback, dirList, resource, srcDir) {
179 if (_.isFunction(extension)) {
180 callback = extension;
181 extension = /.*/;
182 }
183
184 if (_.isArray(pathname)) {
185 var files = pathname;
186 pathname = '';
187 if (!files.length) {
188 return callback(undefined, []);
189 }
190 return handleFiles(false, undefined, _.uniq(files));
191 } else if (!dirList) {
192 if (pathname.src) {
193 resource = resource || pathname;
194 pathname = pathname.src;
195 }
196
197 pathname = exports.resolvePath(pathname);
198 }
199 if (resource && resource.src) {
200 resource = _.clone(resource);
201 delete resource.src;
202 }
203
204 function handleFiles(dirname, err, files, srcDir) {
205 if (err) {
206 return callback(err);
207 }
208
209 var ret = [],
210 count = 0,
211 expected = files.length,
212 prefix = pathname ? pathname.replace(/\/$/, '') + '/' : '';
213
214 function complete(files, index) {
215 count++;
216
217 ret[index] = files;
218
219 if (count === expected) {
220 ret = _.flatten(ret);
221
222 if (srcDir) {
223 ret = ret.map(function(file) {
224 file = resources.cast(file);
225 file.srcDir = srcDir;
226 return file;
227 });
228 }
229
230 if (dirname) {
231 ret.push(_.defaults({dir: dirname}, resource));
232 ret = ret.sort(function(a, b) {
233 return resources.source(a).localeCompare(resources.source(b));
234 });
235 }
236
237 callback(undefined, ret);
238 }
239 }
240
241 if (!files.length) {
242 callback(undefined, []);
243 }
244
245 files.forEach(function(file, index) {
246 var fileResource = resource;
247 if (file.src) {
248 fileResource = resource || file;
249 file = file.src;
250 } else if (_.isObject(file)) {
251 complete(file, index);
252 return;
253 }
254
255 exports.fileList(prefix + file, extension, function(err, files) {
256 if (err) {
257 callback(err);
258 return;
259 }
260
261 complete(files, index);
262 }, dirname, fileResource, srcDir);
263 });
264 }
265
266 exports.stat(pathname, function(err, stat) {
267 if (err) {
268 if (err.code === 'ENOENT') {
269 callback(undefined, [ _.extend({src: exports.makeRelative(pathname), enoent: true}, resource) ]);
270 } else {
271 callback(err);
272 }
273 return;
274 }
275
276 if (stat.isDirectory()) {
277 exports.readdir(pathname, function(err, files) {
278 var _pathname = exports.makeRelative(pathname);
279 handleFiles(_pathname, undefined, files, srcDir || _pathname);
280 });
281 } else {
282 pathname = exports.makeRelative(pathname);
283
284 var basename = path.basename(pathname),
285 namePasses = basename[0] !== '.' && basename !== 'vendor' && (!dirList || extension.test(pathname)),
286 ret = [];
287 if (namePasses) {
288 if (resource) {
289 ret = [ _.defaults({src: pathname, srcDir: srcDir}, resource) ];
290 } else if (srcDir) {
291 ret = [ { src: pathname, srcDir: srcDir } ];
292 } else {
293 ret = [ pathname ];
294 }
295 }
296 callback(undefined, ret);
297 }
298 });
299};
300
301//accepts a template string or a filename ending in .handlebars
302exports.loadTemplate = function(template, splitOnDelimiter, callback) {
303 function compile(templateStr, callback) {
304 try {
305 if (splitOnDelimiter) {
306 callback(null, templateStr.split(splitOnDelimiter).map(function(bit) {
307 return handlebars.compile(bit);
308 }));
309 } else {
310 callback(null, handlebars.compile(templateStr));
311 }
312 } catch (e) {
313 callback(e);
314 }
315 }
316 if (template.match(/\.handlebars$/)) {
317 exports.readFileArtifact(template, 'template', function(err, data) {
318 if (err) {
319 return callback(err);
320 }
321
322 if (data.artifact) {
323 callback(undefined, data.artifact);
324 } else {
325 compile(data.data.toString(), function(err, data) {
326 if (!err) {
327 exports.setFileArtifact(template, 'template', data);
328 }
329 callback(err, data);
330 });
331 }
332 });
333 } else {
334 compile(template, callback);
335 }
336};