-
Notifications
You must be signed in to change notification settings - Fork 56
/
compile._js
421 lines (385 loc) · 13.7 KB
/
compile._js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
"use strict";
/// !doc
///
/// # Compiler and file loader
///
/// `var compiler = require('streamline/lib/compiler/compile')`
///
var fs = require("fs");
var fspath = require("path");
function _exists(callback, fname) {
(fs.exists || fspath.exists)(fname, function(result) {
callback(null, result);
})
}
function mtime(_, fname) {
return _exists(_, fname) ? fs.stat(fname, _).mtime : 0;
}
function _getTransform(options) {
// req variable prevents client side require from getting it as a dependency
var req = require;
if (options.generators) {
return req("../generators/transform");
} else if (options.fibers) {
return req("../fibers/transform");
} else {
return require("../callbacks/transform");
}
}
function _banner(version) {
// important: no newline, to support lines-preserve option!
return "/*** Generated by streamline " + version + " - DO NOT EDIT ***/";
}
function _extend(obj, other) {
for (var i in other) {
obj[i] = other[i];
}
return obj;
}
function _transform(transform, source, options) {
var sourceOptions = /streamline\.options\s*=\s*(\{.*\})/.exec(source);
if (sourceOptions) {
_extend(options, JSON.parse(sourceOptions[1]));
}
options.source = source;
options.callback = options.callback || "_";
options.lines = options.lines || "preserve";
return transform.transform(source, options);
}
function parseShebang(content) {
if (content[0] === '#' && content[1] === '!') {
var n = content.indexOf("\n");
var le = "\n";
if (n != -1) {
var shebang = content.substr(0, n);
if (shebang[shebang.length - 1] == "\r") {
le = "\r\n";
shebang = shebang.substr(0, shebang.length - 1);
}
content = content.substr(n + 1);
return [shebang, content, le];
}
}
return ['', content, ''];
}
exports.compileFile = function(_, js_, options) {
options = _extend({}, options || {});
var ext = fspath.extname(js_);
var language = ext.substring(2);
var basename = fspath.basename(js_, ext);
var dirname = fspath.dirname(js_);
var js = dirname + "/" + basename + ".js";
var mtimejs_ = mtime(_, js_);
var mtimejs = mtime(_, js);
var transform = _getTransform(options);
var banner = _banner(transform.version);
options.sourceName = js_;
var content = fs.readFile(js_, 'utf8', _);
var shebangparse = parseShebang(content);
var shebang = shebangparse[0];
var le = shebangparse[2];
content = shebangparse[1];
if (language === "coffee") {
var coffee = require("../util/require")("coffee-script");
content = coffee.compile(content, {
filename: js_
});
}
banner = shebang + le + banner;
var transformed = mtimejs && fs.readFile(js, 'utf8', _);
if (transformed && mtimejs_ < mtimejs && transformed.substring(0, banner.length) == banner && !options.force) {
return transformed;
}
if (options.verbose) {
console.log("streamline: transforming: " + js_ + " to " + js)
}
var transformed = shebang + banner + _transform(transform, content, options);
if (options.action === 'compile' || !dontSave) {
// try/catch because write will fail if file was installed globally (npm -g)
try {
fs.writeFile(js, transformed, 'utf8', _);
} catch (ex) {}
}
}
var streamlineRE = /require\s*\(\s*['"]streamline\/module['"]\s*\)\s*\(\s*module\s*,?\s*([^)]*)?\s*\)/;
// * `script = compiler.loadFile(_, path, options)`
// Loads Javascript file and transforms it if necessary.
// Returns the transformed source.
// If `path` is `foo_.js`, the source is transformed and the result
// is *not* saved to disk.
// If `path` is `foo.js` and if a `foo_.js` file exists,
// `foo_.js` is transformed if necessary and saved as `foo.js`.
// If `path` is `foo.js` and `foo_.js` does not exist, the contents
// of `foo.js` is returned.
// `options` is a set of options passed to the transformation engine.
// If `options.force` is set, `foo_.js` is transformed even if
// `foo.js` is more recent.
exports.loadFile = function(_, path, options) {
options = _extend({}, options || {});
var ext = fspath.extname(path);
if (ext !== '.js' && ext !== '._js') {
// special hack for streamline-require
if (_exists(_, path + '._js')) path = path + (ext = '._js');
else if (_exists(_, path + '.js')) path = path + (ext = '.js');
else return;
}
var basename = fspath.basename(path, ext);
var dirname = fspath.dirname(path);
var mtimejs, mtimejs_;
var dontSave = basename[basename.length - 1] == '_';
var jsbase = dontSave ? basename.substr(0, basename.length - 1) : basename;
var js = dirname + '/' + jsbase + ext;
var js_ = dirname + '/' + jsbase + '_' + ext;
var fiberjs = dirname + '/' + jsbase + '--fibers' + ext;
if (options.fibers && (mtimejs = mtime(_, fiberjs))) {
js = fiberjs;
} else {
mtimejs = mtime(_, js);
}
mtimejs_ = mtime(_, js_);
options.lines = options.lines || "preserve";
var transform = _getTransform(options);
var banner = _banner(transform.version);
if (mtimejs_) {
options.sourceName = js_;
var content = fs.readFile(js_, 'utf8', _);
var shebangparse = parseShebang(content);
var shebang = shebangparse[0];
var le = shebangparse[2];
content = shebangparse[1];
banner = shebang + le + banner;
var transformed = mtimejs && fs.readFile(js, 'utf8', _);
if (transformed && mtimejs_ < mtimejs && transformed.substring(0, banner.length) == banner && !options.force) {
return transformed;
}
if (options.verbose) {
console.log("streamline: transforming: " + js_ + " to " + js)
}
var transformed = shebang + banner + _transform(transform, content, options);
if (options.action === 'compile' || !dontSave) {
// try/catch because write will fail if file was installed globally (npm -g)
try {
fs.writeFile(js, transformed, 'utf8', _);
} catch (ex) {}
}
return transformed;
} else {
options.sourceName = js;
var content = fs.readFile(js, 'utf8', _);
var matches;
if (ext === '._js') {
return cachedTransform(_, content, path, transform, banner, options);
} else if (matches = streamlineRE.exec(content)) {
content = removeMarker(content, matches, options);
return cachedTransform(_, content, path, transform, banner, options);
} else {
return content;
}
}
}
function mtimeSync(fname) {
try {
return fs.statSync(fname).mtime;
} catch (ex) {
return 0;
}
}
function removeMarker(content, matches, options) {
try {
matches[1] && _extend(options, JSON.parse(matches[1]));
} catch (ex) {
throw new Error("Invalid JSON syntax for streamline options: " + matches[1]);
}
return content.substring(0, matches.index) + "true" + content.substring(matches.index + matches[0].length);
}
exports.transformModule = function(content, path, options) {
options = _extend({}, options || {});
var ext = fspath.extname(path);
var basename = fspath.basename(path, ext);
var dirname = fspath.dirname(path);
var mtimejs, mtimejs_;
var dontSave = basename[basename.length - 1] == '_';
var jsbase = dontSave ? basename.substr(0, basename.length - 1) : basename;
var js = dirname + '/' + jsbase + ext;
var js_ = dirname + '/' + jsbase + '_' + ext;
var fiberjs = dirname + '/' + jsbase + '--fibers' + ext;
if (options.fibers && (mtimejs = mtimeSync(fiberjs))) {
js = fiberjs;
} else {
mtimejs = mtimeSync(js);
}
mtimejs_ = mtimeSync(js_);
options.lines = options.lines || "preserve";
var transform = _getTransform(options);
var banner = _banner(transform.version);
if (mtimejs_) {
options.sourceName = js_;
if (!dontSave) // reload content from js_ file.
content = fs.readFileSync(js_, 'utf8');
var shebangparse = parseShebang(content);
var shebang = shebangparse[0];
var le = shebangparse[2];
content = shebangparse[1];
banner = shebang + le + banner;
var transformed = mtimejs && fs.readFileSync(js, 'utf8');
if (transformed && mtimejs_ < mtimejs && transformed.substring(0, banner.length) == banner && !options.force) return transformed;
if (options.verbose) console.log("streamline: transforming: " + js_)
var transformed = banner + _transform(transform, content, options);
if (!dontSave) {
// try/catch because write will fail if file was installed globally (npm -g)
try {
fs.writeFileSync(js, transformed, 'utf8');
} catch (ex) {}
}
return transformed;
} else {
options.sourceName = path;
var matches;
if (ext !== '.js' && ext !== '.coffee') {
// we don't care about shebang here, but keep line ending if it had a shebang for line counts
var shebangparse = parseShebang(content);
var shebang = shebangparse[0];
content = shebangparse[2] + shebangparse[1];
return transform.transform(content, options);
} else if (matches = streamlineRE.exec(content)) {
content = removeMarker(content, matches, options);
return cachedTransformSync(content, path, transform, banner, options);
} else {
return content;
}
}
}
if (process.env.HOME === undefined && process.env.HOMEDRIVE === undefined) throw new Error("HOME not found, unable to store Streamline callback cache");
var root = (process.env.HOME || (process.env.HOMEDRIVE + process.env.HOMEPATH).replace(/\\/g, '/')) + "/.streamline";
var dirMode = parseInt('777', 8);
function mkdirs(_, path) {
var p = "",
i = 0;
var segs = path.split('/').slice(0, -1);
while (i < segs.length) {
var seg = segs[i];
p += (i++ ? '/' : '') + seg;
if (i > 1 && !_exists(_, p)) fs.mkdir(p, dirMode, _);
}
}
function subdir(options) {
return options.generators ? 'generators' : options.fibers ? 'fibers' : 'callbacks';
}
function cachedTransform(_, content, path, transform, banner, options) {
path = path.replace(/\\/g, '/');
var i = path.indexOf('node_modules/');
if (i < 0) i = path.lastIndexOf('/');
else i += 'node_modules'.length;
var dir = root + '/' + subdir(options);
dir += '/' + path.substring(0, i).replace(/[\/\:]/g, '__');
var f = dir + path.substring(i);
mkdirs(_, f);
var transformed;
if (mtime(_, f) > mtime(_, path)) {
transformed = fs.readFile(f, "utf8", _);
if (transformed.substring(0, banner.length) === banner) return transformed;
}
// no luck in cache
if (options.verbose) console.log("streamline: transforming: " + path);
options.lines = "preserve";
transformed = banner + _transform(transform, content, options);
if (path.indexOf('/tmp--') < 0) fs.writeFile(f, transformed, "utf8", _);
return transformed;
}
function mkdirsSync(path) {
var p = "",
i = 0;
path.split('/').slice(0, -1).forEach(function(seg) {
p += (i++ ? '/' : '') + seg;
if (i > 1 && !(fs.existsSync || fspath.existsSync)(p)) fs.mkdirSync(p, dirMode);
});
}
function cachedTransformSync(content, path, transform, banner, options) {
path = path.replace(/\\/g, '/');
var i = path.indexOf('node_modules/');
if (i < 0) i = path.lastIndexOf('/');
else i += 'node_modules'.length;
var dir = root + '/' + subdir(options);
dir += '/' + path.substring(0, i).replace(/[\/:]/g, '__');
var f = dir + path.substring(i);
mkdirsSync(f);
var transformed;
if (mtimeSync(f) > mtimeSync(path)) {
transformed = fs.readFileSync(f, "utf8");
if (transformed.substring(0, banner.length) === banner) return transformed;
}
// no luck in cache
if (options.verbose) console.log("streamline: transforming: " + path);
var opts = Object.keys(options).reduce(function(r, k) {
r[k] = options[k];
return r;
}, {});
opts.lines = "preserve";
transformed = banner + _transform(transform, content, opts);
if (path.indexOf('/tmp--') < 0) fs.writeFileSync(f, transformed, "utf8");
return transformed;
}
exports.cachedTransformSync = function(content, path, transform, options) {
var banner = _banner(transform.version);
return cachedTransformSync(content, path, {
transform: transform
}, banner, options);
}
/// * `compiler.compile(_, paths, options)`
/// Compiles streamline source files in `paths`.
/// Generates a `foo.js` file for each `foo._js` file found in `paths`.
/// `paths` may be a list of files or a list of directories which
/// will be traversed recursively.
/// `options` is a set of options for the `transform` operation.
exports.compile = function(_, paths, options) {
function _compile(_, path, options) {
var stat = fs.stat(path, _);
if (stat.isDirectory()) {
fs.readdir(path, _).forEach_(_, function(_, f) {
_compile(_, path + "/" + f, options)
});
} else if (stat.isFile()) {
try {
exports.loadFile(_, path, options);
var ext = fspath.extname(path);
if (ext === "._js" || ext === "._coffee") {
exports.compileFile(_, path, options);
} else if (ext === ".coffee" && path[path.length - ext.length - 1] !== '_') {
var jsPath = path.substring(0, path.length - ext.length) + ".js";
if (options.force || mtime(_, path) > mtime(_, jsPath)) {
var source = fs.readFile(path, "utf8", _);
var coffee = require("../util/require")("coffee-script");
var compiled = coffee.compile(source, {
filename: path
});
var matches = streamlineRE.exec(compiled);
if (matches) {
if (options.verbose) console.log("streamline: transforming: " + path + " to " + jsPath);
compiled = removeMarker(compiled, matches, options);
compiled = _transform(_getTransform(options), compiled, options);
} else {
if (options.verbose) console.log("streamline: coffee compiling: " + path + " to " + jsPath);
}
fs.writeFile(jsPath, compiled, "utf8", _);
}
} else {
exports.loadFile(_, path, options);
}
} catch (ex) {
console.error(ex.stack);
failed++;
}
}
// else ignore
}
var failed = 0;
options = options || {};
var transform = _getTransform(options);
if (options.verbose) console.log("transform version: " + transform.version)
if (!paths || paths.length == 0) throw new Error("cannot compile: no files specified");
var cwd = process.cwd();
paths.forEach_(_, function(_, path) {
_compile(_, fspath.resolve(cwd, path), options);
});
if (failed) throw new Error("errors found in " + failed + " files");
};