UNPKG

13.5 kBPlain TextView Raw
1#!/usr/bin/env node
2
3var path = require('path');
4var fs = require('fs');
5var package = require('../package.json');
6var compiler = require("../compiler.imba.js");
7var helpers = compiler.helpers;
8
9function iter$(a){ return a ? (a.toArray ? a.toArray() : a) : []; };
10
11var self = {};
12// imba$v2=0
13
14var imbac1;
15
16var stdin = process.stdin;
17var stdout = process.stdout;
18var ansi = helpers.ansi;
19
20
21var parseOpts = {
22 alias: {
23 o: 'output',
24 h: 'help',
25 s: 'stdio',
26 p: 'print',
27 m: 'sourcemap',
28 t: 'tokenize',
29 v: 'version',
30 w: 'watch',
31 d: 'debug'
32 },
33
34 schema: {
35 output: {type: 'string'},
36 sourcemap: {type: 'string'}, // inline | extern
37 platform: {type: 'string'}, // node | browser | worker
38 styles: {type: 'string'}, // extern | inline
39 imbaPath: {type: 'string'}, // global | inline | import
40 format: {type: 'string'}, // cjs | esm
41 hmr: null,
42 raw: null
43 },
44
45 groupz: ['sourcemap']
46};
47
48var help = `\n
49Usage: imbac [options] path/to/script.imba
50
51 -h, --help display this help message
52 -v, --version display the version number
53 -w, --watch recompile files on change
54 -m, --sourcemap generate source map and add inline to .js files
55 -o, --output [dir] set the output directory for compiled JavaScript
56 -p, --print print out the compiled JavaScript
57 -d, --debug Enable compiler debugging
58 -s, --stdio listen for and compile scripts over stdio
59 -t, --tokenize print out the tokens that the lexer/rewriter produce
60 --platform [type] Specify platform: 'node', 'browser', 'worker'
61 --format [type] Specify format: 'esm' (default), 'cjs'
62 --styles [format] Specify how styles should be added: 'extern', 'inline'
63 --silent only print out errors (skip warnings)
64 --hmr compile for hot-module-reloading
65 --analyze print out the scopes and variables of your script
66`;
67
68function CLI(options){
69 if(options === undefined) options = {};
70 this._options = options;
71 this._sources = [];
72 this._current = null;
73 if(!options.fs) Object.defineProperty(options,'fs',{value: fs, enumerable:false});
74 if(!options.path) Object.defineProperty(options,'path',{value: path, enumerable:false});
75
76 this;
77};
78
79
80CLI.prototype.sources = function(v){ return this._sources; }
81CLI.prototype.setSources = function(v){ this._sources = v; return this; };
82
83CLI.prototype.ensureDir = function (src){
84 if (fs.existsSync(src)) { return true };
85 var parts = path.normalize(src).split(path.sep);
86 for (let i = 0, items = iter$(parts), len = items.length, part; i < len; i++) {
87 part = items[i];
88 if (i < 1) { continue; };
89 // what about relative paths here? no good? might be important for symlinks etc no?
90 var dir = parts.slice(0,i + 1).join(path.sep);
91
92 if (fs.existsSync(dir)) {
93 var stat = fs.statSync(dir);
94 } else if (part.match(/\.(imba\d?|js)$/)) {
95 true;
96 } else {
97 fs.mkdirSync(dir);
98 console.log(ansi.green(("+ mkdir " + dir)));
99 };
100 };
101 return;
102};
103
104CLI.prototype.findRecursive = function (root,pattern){
105 var m;
106 if(pattern === undefined) pattern = /\.imba\d?$/;
107 var results = [];
108 root = path.relative(process.cwd(),root);
109 root = path.normalize(root);
110
111 var read = function(src,depth) {
112 src = path.normalize(src);
113 var stat = fs.statSync(src);
114
115 if (stat.isDirectory() && depth > 0) {
116 var files = fs.readdirSync(src);
117 let res = [];
118 for (let i = 0, items = iter$(files), len = items.length; i < len; i++) {
119 res.push(read(src + '/' + items[i],depth - 1));
120 };
121 return res;
122 } else if (src.match(pattern)) {
123 return results.push(src);
124 };
125 };
126
127 if (m = root.match(/\/\*\.imba\d?$/)) {
128 root = root.slice(0,-m[0].length);
129 read(root,1);
130 } else {
131 read(root,10);
132 };
133
134 return results;
135};
136
137CLI.prototype.pathToSource = function (src,coll,o,root){
138 if(root === undefined) root = null;
139 var abs = path.resolve(process.cwd(),src);
140 var stat = fs.statSync(abs);
141
142 if (stat.isDirectory()) {
143 // console.log "is directory",findRecursive(abs)
144 var files = this.findRecursive(abs);
145 for (let i = 0, items = iter$(files), len = items.length; i < len; i++) {
146 this.pathToSource(items[i],coll,o,abs);
147 };
148 return;
149 };
150
151 var file = {
152 filename: path.basename(src),
153 sourcePath: abs,
154 sourceBody: fs.readFileSync(abs,'utf8')
155 };
156
157 if (o.output) {
158 var rel = root ? path.relative(root,abs) : file.filename;
159 file.targetPath = path.resolve(o.output,rel);
160 } else if (!o.stdio) {
161 file.targetPath = file.sourcePath;
162 };
163
164 if (file.targetPath) {
165 file.targetPath = file.targetPath.replace(/\.imba\d?$/,'.js');
166 };
167
168 return coll.push(file);
169};
170
171
172CLI.prototype.cwd = function (){
173 return process.cwd();
174};
175
176CLI.prototype.run = function (){
177 var self = this, o_, main_;
178 (o_ = self.o()).platform || (o_.platform = 'node');
179
180 if (self.o().output) {
181 self.o().output = path.normalize(path.resolve(self.cwd(),self.o().output));
182 };
183
184 var paths = ((typeof (main_ = self.o().main)=='string'||main_ instanceof String)) ? [self.o().main] : ((self.o().main || []));
185 for (let i = 0, items = iter$(paths), len = items.length; i < len; i++) {
186 self.pathToSource(items[i],self._sources,self._options);
187 };
188
189 if (self.o().stdio) {
190 if (!self.o().output) { self.o().print = true };
191 var chunks = [];
192 stdin.resume();
193 stdin.setEncoding('utf8');
194 stdin.on('data',function(chunk) { return chunks.push(chunk); });
195 stdin.on('end',function() {
196 self.sources().push({sourcePath: 'stdin',sourceBody: chunks.join()});
197 return self.finish();
198 });
199 } else {
200 self.finish();
201 };
202 return self;
203};
204
205CLI.prototype.o = function (){
206 return this._options;
207};
208
209CLI.prototype.traverse = function (cb){
210 for (let i = 0, items = iter$(this.sources()), len = items.length, src; i < len; i++) {
211 src = items[i];
212 this._current = src;
213 cb(src);
214 };
215 return this;
216};
217
218CLI.prototype.log = function (message){
219 if (!this.o().print) { console.log(message) };
220 return this;
221};
222
223CLI.prototype.b = function (text){
224 return this.o().colors ? ansi.bold(text) : text;
225};
226
227CLI.prototype.gray = function (text){
228 return this.o().colors ? ansi.gray(text) : text;
229};
230
231CLI.prototype.red = function (text){
232 return this.o().colors ? ansi.red(text) : text;
233};
234
235CLI.prototype.green = function (text){
236 return this.o().colors ? ansi.green(text) : text;
237};
238
239CLI.prototype.yellow = function (text){
240 return this.o().colors ? ansi.yellow(text) : text;
241};
242
243CLI.prototype.rel = function (src){
244 src = src.sourcePath || src;
245 return path.relative(process.cwd(),src);
246};
247
248CLI.prototype.present = function (data){
249 if (this.o().print) {
250 process.stdout.write(data);
251 };
252 return this;
253};
254
255CLI.prototype.analyze = function (){
256 var self = this;
257 return self.traverse(function(src) {
258 var o2 = Object.create(self.o());
259 o2.sourcePath = src.sourcePath;
260 o2.entities = true;
261 var out = compiler.analyze(src.sourceBody,o2);
262 src.analysis = out;
263 return self.present(JSON.stringify(out,null,2));
264 });
265};
266
267CLI.prototype.tokenize = function (){
268 // should prettyprint tokens
269 var self = this;
270 return self.traverse(function(src) {
271 var o2 = Object.create(self.o());
272 o2.sourcePath = src.sourcePath;
273 o2.rewrite = self.o().rewrite;
274 var out
275 try {
276 out = compiler.tokenize(src.sourceBody,o2);
277 } catch(e){
278 console.log('error in tokenizer:',e.message);
279 throw e;
280 out = e._options.tokens;
281 }
282
283 src.tokens = out;
284
285 let res = [];
286 for (let i = 0, items = iter$(src.tokens), len = items.length, t; i < len; i++) {
287 t = items[i];
288 var typ = t._type;
289 var id = t._value;
290 var s;
291 if (typ == 'TERMINATOR') {
292 s = "[" + self.b(id.replace(/\n/g,"\\n").replace(/\t/g,"\\t")) + "]\n";
293 } else if (typ == 'IDENTIFIER') {
294 s = id;
295 } else if (typ == 'NUMBER') {
296 s = id;
297 } else if (id == typ) {
298 s = "[" + self.b(id) + "]";
299 } else {
300 s = self.b(("[" + typ + " " + id + "]"));
301 };
302
303 if (t._loc != -1 && self.o().sourcemap) {
304 s = ("(" + (t._loc) + ":" + (t._len) + ")") + s; // chalk.bold(s)
305 };
306
307 res.push(s);
308 };
309 var strings = res;
310
311 return process.stdout.write(strings.join(' ') + '\n');
312 });
313};
314
315CLI.prototype.compile = function (){
316 var self = this;
317 return self.traverse(function(src) { return self.compileFile(src); });
318};
319
320CLI.prototype.compileFile = function (src){
321 var self = this;
322 var opts = Object.create(self.o());
323 // opts.filename = src.filename;
324 opts.sourcePath = src.sourcePath;
325
326 if(opts.extlib) opts.imbaPath = null;
327
328 var out = {};
329 var t = Date.now();
330 var at = new Date().toTimeString().substr(0,8);
331 var srcp = self.o().stdio ? src.sourcePath : path.relative(process.cwd(),src.sourcePath);
332 var dstp = src.targetPath && path.relative(process.cwd(),src.targetPath);
333
334 var srcpAbs = path.resolve(srcp);
335 var dstpAbs = path.resolve(dstp);
336
337 if (srcp.indexOf("../") >= 0) {
338 srcp = srcpAbs;
339 dstp = dstpAbs;
340 };
341
342 opts.sourcePath = srcpAbs;
343
344 try {
345 if (src.sourcePath.match(/\.imba1$/)) {
346 imbac1 || (imbac1 = require("../script/bootstrap.compiler.js"));
347 opts.filename = opts.sourcePath;
348 out = imbac1.compile(src.sourceBody,opts);
349 } else {
350 out = compiler.compile(src.sourceBody,opts);
351 if(opts.runtime == 'inline'){
352 out.js = fs.readFileSync(path.resolve(__dirname,'..','dist','imba.js'),'utf8') + '\n' + out.js;
353 }
354 };
355 } catch (e) {
356 console.log('error',e);
357 out = {errors: [e]};
358 };
359
360 if(out.errors && out.errors.length){
361 // out = { error: out.errors[0].toError() }
362 }
363
364 out.compileTime = Date.now() - t;
365
366 let sourcemap = opts.sourcemap && out.sourcemap;
367
368 if (sourcemap) {
369 // TODO this should clearly happen inside the compiler??
370 if(opts.sourcemap == 'inline'){
371 // var base64 = new Buffer(JSON.stringify(sourcemap)).toString("base64");
372 // out.js = out.js + "\n//# sourceMappingURL=data:application/json;base64," + base64;
373 } else if (opts.sourcemap && opts.sourcemap != 'external'){
374 out.js = out.js + "\n//# sourceMappingURL=" + path.basename(src.targetPath + '.map');
375 }
376 };
377
378 src.output = out;
379
380 let status = ("" + self.gray(("" + at + " compile")) + " " + srcp + " " + self.gray("to") + " " + dstp + " " + self.green(out.compileTime + "ms"));
381
382 if (out.error) {
383
384 status += self.red(" [1 error]");
385 };
386
387 if (out.warnings && out.warnings.length) {
388 let count = out.warnings.length;
389 status += self.yellow((" [" + count + " warning" + ((count > 1) ? 's' : '') + "]"));
390 };
391
392 if (out.errors && out.errors.length) {
393 let count = out.errors.length;
394 status += self.red((" [" + count + " error" + ((count > 1) ? 's' : '') + "]"));
395 if (!self.o().print) {
396 self.log(status);
397
398 out.errors.forEach(function(err,i){
399 if(err.toError instanceof Function){
400 err = err.toError();
401 }
402 // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {red(out:compileTime + "ms")}"
403 if (err.excerpt) {
404 self.log(" " + err.excerpt({colors: self.o().colors}));
405 } else if(err instanceof SyntaxError && err.stack){
406 self.log(err.stack);
407 } else {
408 self.log(" " + err.message);
409 self.log(" " + ("in file " + srcp));
410 if (err.stack) { self.log(err.stack) };
411 };
412 })
413 };
414 }
415 else if (src.targetPath && out.js !== undefined && !self.o().print) {
416 self.ensureDir(src.targetPath);
417 fs.writeFileSync(src.targetPath,out.js,'utf8');
418 if (!self.o().print) { self.log(status) };
419
420 if(sourcemap && !opts.print && opts.sourcemap != 'inline') { // And webpack
421 fs.writeFileSync(src.targetPath + '.map',JSON.stringify(sourcemap,null,2),'utf8');
422 }
423 // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {green(out:compileTime + "ms")}"
424 };
425
426 if (out.warnings && !self.o().silent && !self.o().print) {
427 // log " {out:warnings:length} warnings"
428 out.warnings.forEach(function(err,i){
429 if(err.toError instanceof Function){
430 err = err.toError();
431 }
432 // log "{gray("{at} compile")} {srcp} {gray("to")} {dstp} {red(out:compileTime + "ms")}"
433 if (err.excerpt) {
434 self.log(" " + err.excerpt({colors: self.o().colors}));
435 } else if(err instanceof SyntaxError && err.stack){
436 self.log(err.stack);
437 } else {
438 self.log(" " + err.message);
439 self.log(" " + ("in file " + srcp));
440 if (err.stack) { self.log(err.stack) };
441 };
442 })
443 // helpers
444 };
445
446
447 if (self.o().watch && !src.watcher) {
448 var now = Date.now();
449 src.watcher = fs.watch(src.sourcePath,function(type,filename) {
450 if (type == 'change') {
451 return setTimeout(function() {
452 return fs.readFile(src.sourcePath,'utf8',function(err,body) {
453 if (body != src.sourceBody) {
454 src.sourceBody = body;
455 return self.compileFile(src);
456 };
457 });
458 },100);
459 };
460 });
461 };
462
463
464 if (self.o().print && out.js) {
465 process.stdout.write(out.js);
466 };
467 return self;
468};
469
470CLI.prototype.finish = function (){
471 try {
472 if (this.o().analyze) {
473 this.o().print = true;
474 this.analyze(this.sources(),this.o());
475 } else if (this.o().tokenize) {
476 this.o().print = true;
477 this.tokenize(this.sources(),this.o());
478 } else {
479 this.compile(this.sources(),this.o());
480 };
481 } catch (e) {
482 if (this._current) {
483 this.log(("ERROR in " + this.b(this.rel(this._current))));
484 };
485 if (e.excerpt) {
486 this.log(e.excerpt({colors: true}));
487 } else {
488 throw e;
489 };
490 };
491 return this;
492};
493
494
495function main(){
496 var o = helpers.parseArgs(process.argv.slice(2),parseOpts);
497
498 (o.colors == null) ? (o.colors = true) : o.colors;
499
500 if (o.version) {
501 return console.log(package.version);
502 } else if (!(o.main || o.stdio) || o.help) {
503 return console.log(help);
504 } else {
505 return new CLI(o).run();
506 };
507};
508
509main();
\No newline at end of file