Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemathinc
GitHub Repository: sagemathinc/python-wasm
Path: blob/main/python/pylang/tools/cli.js
1396 views
1
/*
2
* cli.js
3
* Copyright (C) 2015 Kovid Goyal <kovid at kovidgoyal.net>
4
*
5
* Distributed under terms of the BSD license.
6
*/
7
"use strict"; /*jshint node:true */
8
9
var path = require("path");
10
var utils = require("./utils");
11
var colored = utils.colored;
12
var has_prop = Object.prototype.hasOwnProperty.call.bind(
13
Object.prototype.hasOwnProperty
14
);
15
16
function OptionGroup(name) {
17
this.name = name;
18
this.description = undefined;
19
this.extra = undefined;
20
this.options = {
21
string: {},
22
boolean: {},
23
alias: {},
24
default: {},
25
choices: {},
26
};
27
28
this.help = {};
29
this.seen = {};
30
}
31
32
var groups = {},
33
group;
34
35
function create_group(name, usage, description, extra) {
36
group = new OptionGroup(name);
37
var match = utils.comment_contents.exec(description.toString());
38
if (!match) {
39
throw new TypeError("Multiline comment missing for: " + name);
40
}
41
group.description = match[1];
42
group.usage = name + " [options] " + usage;
43
groups[name] = group;
44
45
if (extra) {
46
match = utils.comment_contents.exec(extra.toString());
47
if (match) group.extra = match[1];
48
}
49
50
opt("help", "h", "bool", false, function () {
51
/*
52
show this help message and exit
53
*/
54
});
55
56
opt("version", "V", "bool", false, function () {
57
/*
58
show the version and exit
59
*/
60
});
61
}
62
63
var COL1 = "yellow",
64
COL2 = "green";
65
66
function print_usage(group) {
67
// {{{
68
var COL_WIDTH = 79;
69
var OPT_WIDTH = 23;
70
71
var usage = group ? group.usage : "[subcommand] ...";
72
console.log(
73
colored("Usage:", COL1),
74
colored(path.basename(process.argv[1]), COL2),
75
usage,
76
"\n"
77
);
78
if (!group) {
79
// Overall usage
80
help =
81
"PyLang can perform many actions, depending on which" +
82
"\nsubcommand is invoked. With no arguments, it will start a REPL," +
83
"\nunless STDIN is a pipe, in which case it will compile whatever" +
84
"\nyou pass on STDIN and write the output to STDOUT. See the full" +
85
"\nlist of subcommands below.";
86
console.log(help, "\n");
87
console.log(colored("Subcommands:", COL1));
88
Object.keys(groups).forEach(function (name) {
89
console.log();
90
var dt = utils.wrap(
91
groups[name].description.split("\n"),
92
COL_WIDTH - OPT_WIDTH
93
);
94
console.log(
95
colored(
96
(name + utils.repeat(" ", OPT_WIDTH)).slice(0, OPT_WIDTH),
97
COL2
98
),
99
dt[0]
100
);
101
dt.slice(1).forEach(function (line) {
102
console.log(utils.repeat(" ", OPT_WIDTH), line);
103
});
104
});
105
return;
106
}
107
108
// Group specific usage
109
110
console.log(group.description);
111
if (group.extra) console.log("\n" + group.extra);
112
console.log(colored("\nOptions:", COL1));
113
var options = group.options;
114
var help = group.help;
115
116
Object.getOwnPropertyNames(options.alias).forEach(function (name) {
117
var optstr = " --" + name.replace(/_/g, "-");
118
options.alias[name].forEach(function (alias) {
119
optstr +=
120
", " + (alias.length > 1 ? "--" : "-") + alias.replace(/_/g, "-");
121
});
122
var ht = utils.wrap(help[name].split("\n"), COL_WIDTH - OPT_WIDTH);
123
124
if (optstr.length > OPT_WIDTH) console.log(colored(optstr, COL2));
125
else {
126
console.log(
127
colored(
128
(optstr + utils.repeat(" ", OPT_WIDTH)).slice(0, OPT_WIDTH),
129
COL2
130
),
131
ht[0]
132
);
133
ht = ht.splice(1);
134
}
135
ht.forEach(function (line) {
136
console.log(utils.repeat(" ", OPT_WIDTH), line);
137
});
138
console.log();
139
});
140
} // }}}
141
142
// Process options {{{
143
144
function opt(name, aliases, type, default_val, help_text, choices) {
145
var match = utils.comment_contents.exec(help_text.toString());
146
var options = group.options;
147
var seen = group.seen;
148
var help = group.help;
149
150
if (!match) {
151
throw new TypeError("Multiline comment missing for: " + name);
152
}
153
help_text = match[1];
154
155
if (!type || type == "bool") options.boolean[name] = true;
156
else if (type == "string") {
157
options.string[name] = true;
158
if (choices) options.choices[name] = choices;
159
}
160
161
if (default_val !== undefined) options.default[name] = default_val;
162
163
if (aliases && aliases.length) {
164
aliases.split(",").forEach(function (alias) {
165
if (has_prop(seen, alias))
166
throw "The option name:" + alias + " has already been used.";
167
seen[alias] = true;
168
});
169
options.alias[name] = aliases.split(",");
170
} else options.alias[name] = [];
171
172
if (has_prop(seen, name))
173
throw "The option name:" + name + " has already been used.";
174
seen[name] = true;
175
176
help[name] = help_text;
177
}
178
// }}}
179
180
function parse_args() {
181
// {{{
182
var ans = { files: [] };
183
var name_map = {};
184
var state, options, group;
185
186
function plain_arg(arg) {
187
if (state !== undefined) ans[state] = arg;
188
else ans.files.push(arg);
189
state = undefined;
190
}
191
192
function handle_opt(arg) {
193
var oarg = arg;
194
var is_long_opt = arg[0] === "-" ? true : false;
195
if (is_long_opt) arg = arg.substr(1);
196
if (state !== undefined) ans[state] = "";
197
state = undefined;
198
if (!is_long_opt && arg.length > 1) {
199
arg.split("").forEach(handle_opt);
200
return;
201
}
202
var val = arg.indexOf("=");
203
if (val > -1) {
204
var t = arg.substr(val + 1);
205
arg = arg.substr(0, val);
206
val = t;
207
} else val = undefined;
208
209
var name = name_map[arg.replace(/-/g, "_")];
210
if (!name) {
211
print_usage(group);
212
console.error(
213
"\nThe option:",
214
colored("-" + oarg, "red"),
215
"is not recognized"
216
);
217
process.exit(1);
218
}
219
if (has_prop(options.boolean, name)) {
220
if (!val) val = "true";
221
if (val === "true" || val === "1") val = true;
222
else if (val === "false" || val === "0") val = false;
223
else {
224
console.error(
225
"The value:",
226
colored(val, "red"),
227
"is invalid for the boolean option:",
228
colored(name, "red")
229
);
230
process.exit(1);
231
}
232
ans[name] = val;
233
} else {
234
if (val !== undefined) ans[name] = val;
235
else state = name;
236
}
237
}
238
239
var all_args = process.argv.slice(2);
240
ans.auto_mode = false;
241
if (has_prop(groups, all_args[0])) {
242
ans.mode = all_args[0];
243
all_args = all_args.slice(1);
244
} else {
245
// this check is not robust, but, it will only fail if the repl mode takes any non-boolean options
246
var has_files =
247
all_args.filter(function (a) {
248
return a[0] !== "-";
249
}).length > 0;
250
ans.mode = !has_files ? "repl" : "compile";
251
if (has_files) {
252
ans.execute = true;
253
}
254
ans.auto_mode = true;
255
}
256
options = groups[ans.mode].options;
257
258
Object.getOwnPropertyNames(options.default).forEach(function (name) {
259
if (ans[name] == null) {
260
ans[name] = options["default"][name];
261
}
262
});
263
264
Object.getOwnPropertyNames(options.alias).forEach(function (name) {
265
name_map[name] = name;
266
options.alias[name].forEach(function (alias) {
267
name_map[alias] = name;
268
});
269
});
270
271
var options_ended = false;
272
273
all_args.forEach(function (arg) {
274
if (options_ended) plain_arg(arg);
275
else if (arg === "--") options_ended = true;
276
else if (arg === "-") plain_arg(arg);
277
else if (arg[0] === "-") handle_opt(arg.substr(1));
278
else plain_arg(arg);
279
});
280
if (state !== undefined) plain_arg("");
281
Object.keys(options.choices).forEach(function (name) {
282
var allowed = options.choices[name];
283
if (allowed.indexOf(ans[name]) < 0) {
284
print_usage(groups[ans.mode]);
285
console.error(
286
'The value "' +
287
colored(ans[name], "red") +
288
'" is not allowed for ' +
289
colored(name, "red") +
290
". Allowed values: " +
291
options.choices[name].join(", ")
292
);
293
process.exit(1);
294
}
295
});
296
return ans;
297
} // }}}
298
299
create_group("compile", "[input1.py input2.py ...]", function () {
300
/*
301
Compile PyLang source code into JavaScript
302
output. You can also pipe the source code into
303
stdin.
304
*/
305
});
306
307
opt("output", "o", "string", "", function () {
308
/*
309
Output file (default STDOUT)
310
*/
311
});
312
313
opt("bare", "b", "bool", false, function () {
314
/*
315
Remove the module wrapper that prevents PyLang
316
scope from bleeding into other JavaScript logic
317
*/
318
});
319
320
opt("keep_docstrings", "d", "bool", false, function () {
321
/*
322
Keep the docstrings in the generated JavaScript as __doc__
323
attributes on functions, classes and modules. Normally,
324
the docstring are deleted to reduce download size.
325
*/
326
});
327
328
opt("discard_asserts", "a", "bool", false, function () {
329
/*
330
Discard any assert statements. If you use assert statements
331
for debugging, then use this option to generate an optimized build
332
without the assert statements.
333
*/
334
});
335
336
opt("omit_baselib", "m", "bool", false, function () {
337
/*
338
Omit baselib functions. Use this if you have a
339
different way of ensuring they're imported. For example,
340
you could import one of the baselib-plain-*.js files directly
341
into the global namespace.
342
*/
343
});
344
345
opt("import_path", "p", "string", "", function () {
346
/*
347
A list of paths in which to look for imported modules.
348
Multiple paths must be separated by the path separator
349
(: on Unix and ; on Windows). You can also use the
350
environment variable PYLANGPATH for this,
351
with identical syntax. Note that these directories
352
are searched before the builtin paths, which means you
353
can use them to replace builtin modules.
354
*/
355
});
356
357
opt("filename_for_stdin", "P", "string", "", function () {
358
/*
359
filename to use for data piped into STDIN. Imports will
360
be resolved relative to the directory this filename is in.
361
Note, that you can also use the --import-path option to
362
add directories to the import path.
363
*/
364
});
365
366
opt("cache_dir", "C", "string", "", function () {
367
/*
368
directory to use to store the cached files generated
369
by the compiler. If set to '' (the default) then no
370
cached files are stored at all.
371
*/
372
});
373
374
opt("comments", undefined, "string", "", function () {
375
/*
376
Preserve copyright comments in the output.
377
By default this works like Google Closure, keeping
378
JSDoc-style comments that contain "@license" or
379
"@preserve". You can optionally pass one of the
380
following arguments to this flag:
381
- "all" to keep all comments
382
- a valid JS regexp (needs to start with a slash) to
383
keep only comments that match.
384
385
Note that currently not *all* comments can be kept
386
when compression is on, because of dead code removal
387
or cascading statements into sequences.
388
*/
389
});
390
391
opt("stats", undefined, "bool", false, function () {
392
/*
393
Display operations run time on STDERR.
394
*/
395
});
396
397
opt("execute", "x,exec", "bool", false, function () {
398
/*
399
Compile and execute the PyLang code, all in
400
one invocation. Useful if you wish to use PyLang for
401
scripting. Note that you can also use the -o option to
402
have the compiled JavaScript written out to a file
403
before being executed. If you specify this option you
404
should not specify the -m option to omit the baselib, or
405
execution will fail.
406
*/
407
});
408
409
create_group("repl", "", function () {
410
/*
411
Run a Read-Eval-Print-Loop (REPL). This allows
412
you to type and run PyLang at a live
413
command prompt. Type show_js=True to show Javascript.
414
*/
415
});
416
417
opt("no_js", "", "bool", true, function () {
418
/*
419
Do not display the compiled JavaScript before executing
420
it.
421
*/
422
});
423
424
opt("jsage", "", "bool", false, function () {
425
/*
426
Enable everything implemented from our Sage-style preparser
427
*/
428
});
429
430
opt("tokens", "", "bool", false, function () {
431
/*
432
Show every token as they are parsed.
433
*/
434
});
435
436
create_group(
437
"lint",
438
"[input1.py input2.py ...]",
439
function () {
440
/*
441
Run the PyLang linter. This will find various
442
possible problems in the .py files you specify and
443
write messages about them to stdout. Use - to read from STDIN.
444
The main check it performs is for unused/undefined
445
symbols, like pyflakes does for python.
446
*/
447
},
448
function () {
449
/*
450
In addition to the command line options listed below,
451
you can also control the linter in a couple of other ways.
452
453
In the actual source files, you can turn off specific checks
454
on a line by line basis by adding: # noqa:check1,check2...
455
to the end of the line. For example:
456
457
f() # noqa: undef
458
459
will prevent the linter from showing undefined symbol
460
errors for this line. You can also turn off individual checks
461
at the file level, by putting the noqa directive on a
462
line by itself near the top of the file, for example:
463
464
# noqa: undef
465
466
Similarly, you can tell the linter
467
about global (builtin) symbols with a comment near the top
468
of the file, for example:
469
470
# globals:assert,myglobalvar
471
472
This will prevent the linter form treating these names as
473
undefined symbols.
474
475
Finally, the linter looks for a setup.cfg file in the
476
directory containing the file being linted or any of its
477
parent directories. You can both turn off individual checks
478
and define project specific global symbols in the setup.cfg
479
file, like this:
480
481
[rapydscript]
482
globals=myglobalvar,otherglobalvar
483
noqa=undef,eol-semicolon
484
485
*/
486
}
487
);
488
489
opt("globals", "g,b,builtins", "string", "", function () {
490
/*
491
Comma separated list of additional names that the linter will
492
treat as global symbols. It ignores undefined errors for
493
global symbols.
494
*/
495
});
496
497
opt("noqa", "e,ignore,exclude", "string", "", function () {
498
/*
499
Comma separated list of linter checks to skip. The linter
500
will not report errors corresponding to these checks.
501
The check names are output in the linter's normal output, you
502
can also list all check names with --noqa-list.
503
*/
504
});
505
506
opt(
507
"errorformat",
508
"f,s,style",
509
"string",
510
"human",
511
function () {
512
/*
513
Output the results in the specified format. Valid formats are:
514
human - output is suited for reading by humans (the default)
515
json - output is in JSON format
516
vim - output can be consumed easily by vim's errorformat
517
directive. Format is:
518
filename:line:col:errortype:token:message [identifier]
519
undef - output only the names of undefined symbols in a form that
520
can be easily copy/pasted
521
*/
522
},
523
["human", "json", "vim", "undef"]
524
);
525
526
opt("noqa_list", "", "bool", false, function () {
527
/*
528
List all available linter checks, with a brief
529
description, and exit.
530
*/
531
});
532
533
opt("stdin_filename", "", "string", "STDIN", function () {
534
/*
535
The filename for data read from STDIN. If not specified
536
STDIN is used.
537
*/
538
});
539
540
create_group("test", "[test1 test2...]", function () {
541
/*
542
Run PyLang tests. You can specify the name of
543
individual test files to only run tests from those
544
files. For example:
545
test baselib functions
546
*/
547
});
548
549
create_group("self", "", function () {
550
/*
551
Compile the compiler itself. It will only actually
552
compile if something has changed since the last time
553
it was called. To force a recompilation, simply
554
delete lib/signatures.json
555
*/
556
});
557
558
opt("complete", "c,f,full", "bool", false, function () {
559
/*
560
Run the compilation repeatedly, as many times as neccessary,
561
so that the compiler is built with the most up to date version
562
of itself.
563
*/
564
});
565
566
opt("test", "t", "bool", false, function () {
567
/*
568
Run the test suite after building completes.
569
*/
570
});
571
572
opt("profile", "p", "bool", false, function () {
573
/*
574
Run a CPU profiler which will output its data to
575
self.cpuprofile. The data can then be analysed with
576
node-inspector.
577
*/
578
});
579
580
opt("omit_header", "m", "bool", false, function () {
581
/*
582
Do not write header with 'msgid ""' entry.
583
*/
584
});
585
586
opt("package_name", "", "string", "XXX", function () {
587
/*
588
Set the package name in the header
589
*/
590
});
591
592
opt("base_path", "", "string", "", function () {
593
/*
594
Sets the base path of the source code, instead of
595
automatically determining it from the bin.
596
This is very useful since it allows us to compile
597
the source of one version of the compiler using
598
a binary distribution of an older version, hence
599
we can bootstrap without having to store binaries
600
in our Git repo.
601
*/
602
});
603
604
opt("package_version", "", "string", "XXX", function () {
605
/*
606
Set the package version in the header
607
*/
608
});
609
610
opt("bugs_address", "bug_address", "string", "[email protected]", function () {
611
/*
612
Set the email address for bug reports in the header
613
*/
614
});
615
616
create_group(
617
"msgfmt",
618
"",
619
function () {
620
/*
621
Compile a .po file into a .json file that can
622
be used to load translations in a browser.
623
*/
624
},
625
function () {
626
/*
627
The .po file is read from
628
stdin and the .json file written to stdout. Note
629
that it is assumed the .po file is encoded in UTF-8.
630
If you .po file is in some other encoding, you will need to
631
convert it to UTF-8 first.
632
*/
633
}
634
);
635
636
opt("use_fuzzy", "f", "bool", false, function () {
637
/*
638
Use fuzzy translations, they are ignored by default.
639
*/
640
});
641
642
var argv = (module.exports.argv = parse_args());
643
644
if (argv.help) {
645
print_usage(!argv.auto_mode ? groups[argv.mode] : undefined);
646
process.exit(0);
647
}
648
649
if (argv.version) {
650
var json = require("../package.json");
651
console.log(json.name + " " + json.version);
652
process.exit(0);
653
}
654
655