1 | var _ = 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 |
|
8 | const EMFILE_RETRY = 250;
|
9 |
|
10 | var fileCache = {};
|
11 |
|
12 | exports = module.exports = new EventEmitter();
|
13 |
|
14 | function 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 |
|
50 | exports.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 |
|
62 | var lookupPath;
|
63 | exports.resolvePath = function(pathName) {
|
64 |
|
65 |
|
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 | };
|
74 | exports.makeRelative = function(pathName) {
|
75 | if (pathName.indexOf(lookupPath) === 0) {
|
76 | return pathName.substring(lookupPath.length);
|
77 | } else {
|
78 | return pathName;
|
79 | }
|
80 | };
|
81 |
|
82 | exports.lookupPath = function(pathName) {
|
83 | if (pathName !== undefined) {
|
84 | lookupPath = pathName;
|
85 | if (lookupPath && !/\/$/.test(lookupPath)) {
|
86 | lookupPath += '/';
|
87 | }
|
88 | }
|
89 | return lookupPath;
|
90 | };
|
91 |
|
92 | exports.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 |
|
102 | exports.readFileSync = function(file) {
|
103 | return fs.readFileSync(exports.resolvePath(file));
|
104 | };
|
105 | exports.readFile = function(file, callback) {
|
106 | cacheRead(file, fs.readFile.bind(fs), function(err, cache) {
|
107 | callback(err, cache && cache.data);
|
108 | });
|
109 | };
|
110 | exports.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 | };
|
116 | exports.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 |
|
125 | exports.readdir = function(dir, callback) {
|
126 | cacheRead(dir, fs.readdir.bind(fs), function(err, cache) {
|
127 | callback(err, cache && cache.data);
|
128 | });
|
129 | };
|
130 |
|
131 | exports.ensureDirs = function(pathname, callback) {
|
132 | var dirname = path.dirname(pathname);
|
133 | exports.stat(dirname, function(err) {
|
134 | if (err && err.code === 'ENOENT') {
|
135 |
|
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 |
|
142 | callback(err && err.code === 'EEXIST' ? undefined : err);
|
143 | }
|
144 | });
|
145 | });
|
146 | } else {
|
147 | callback();
|
148 | }
|
149 | });
|
150 | };
|
151 |
|
152 | exports.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 |
|
172 |
|
173 |
|
174 |
|
175 |
|
176 |
|
177 |
|
178 | exports.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 |
|
302 | exports.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 | };
|