1 | /**
|
---|
2 | * Module dependencies.
|
---|
3 | */
|
---|
4 |
|
---|
5 | var EventEmitter = require('events').EventEmitter;
|
---|
6 | var spawn = require('child_process').spawn;
|
---|
7 | var path = require('path');
|
---|
8 | var dirname = path.dirname;
|
---|
9 | var basename = path.basename;
|
---|
10 | var fs = require('fs');
|
---|
11 |
|
---|
12 | /**
|
---|
13 | * Inherit `Command` from `EventEmitter.prototype`.
|
---|
14 | */
|
---|
15 |
|
---|
16 | require('util').inherits(Command, EventEmitter);
|
---|
17 |
|
---|
18 | /**
|
---|
19 | * Expose the root command.
|
---|
20 | */
|
---|
21 |
|
---|
22 | exports = module.exports = new Command();
|
---|
23 |
|
---|
24 | /**
|
---|
25 | * Expose `Command`.
|
---|
26 | */
|
---|
27 |
|
---|
28 | exports.Command = Command;
|
---|
29 |
|
---|
30 | /**
|
---|
31 | * Expose `Option`.
|
---|
32 | */
|
---|
33 |
|
---|
34 | exports.Option = Option;
|
---|
35 |
|
---|
36 | /**
|
---|
37 | * Initialize a new `Option` with the given `flags` and `description`.
|
---|
38 | *
|
---|
39 | * @param {String} flags
|
---|
40 | * @param {String} description
|
---|
41 | * @api public
|
---|
42 | */
|
---|
43 |
|
---|
44 | function Option(flags, description) {
|
---|
45 | this.flags = flags;
|
---|
46 | this.required = flags.indexOf('<') >= 0;
|
---|
47 | this.optional = flags.indexOf('[') >= 0;
|
---|
48 | this.bool = flags.indexOf('-no-') === -1;
|
---|
49 | flags = flags.split(/[ ,|]+/);
|
---|
50 | if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
|
---|
51 | this.long = flags.shift();
|
---|
52 | this.description = description || '';
|
---|
53 | }
|
---|
54 |
|
---|
55 | /**
|
---|
56 | * Return option name.
|
---|
57 | *
|
---|
58 | * @return {String}
|
---|
59 | * @api private
|
---|
60 | */
|
---|
61 |
|
---|
62 | Option.prototype.name = function() {
|
---|
63 | return this.long
|
---|
64 | .replace('--', '')
|
---|
65 | .replace('no-', '');
|
---|
66 | };
|
---|
67 |
|
---|
68 | /**
|
---|
69 | * Return option name, in a camelcase format that can be used
|
---|
70 | * as a object attribute key.
|
---|
71 | *
|
---|
72 | * @return {String}
|
---|
73 | * @api private
|
---|
74 | */
|
---|
75 |
|
---|
76 | Option.prototype.attributeName = function() {
|
---|
77 | return camelcase(this.name());
|
---|
78 | };
|
---|
79 |
|
---|
80 | /**
|
---|
81 | * Check if `arg` matches the short or long flag.
|
---|
82 | *
|
---|
83 | * @param {String} arg
|
---|
84 | * @return {Boolean}
|
---|
85 | * @api private
|
---|
86 | */
|
---|
87 |
|
---|
88 | Option.prototype.is = function(arg) {
|
---|
89 | return this.short === arg || this.long === arg;
|
---|
90 | };
|
---|
91 |
|
---|
92 | /**
|
---|
93 | * Initialize a new `Command`.
|
---|
94 | *
|
---|
95 | * @param {String} name
|
---|
96 | * @api public
|
---|
97 | */
|
---|
98 |
|
---|
99 | function Command(name) {
|
---|
100 | this.commands = [];
|
---|
101 | this.options = [];
|
---|
102 | this._execs = {};
|
---|
103 | this._allowUnknownOption = false;
|
---|
104 | this._args = [];
|
---|
105 | this._name = name || '';
|
---|
106 | }
|
---|
107 |
|
---|
108 | /**
|
---|
109 | * Add command `name`.
|
---|
110 | *
|
---|
111 | * The `.action()` callback is invoked when the
|
---|
112 | * command `name` is specified via __ARGV__,
|
---|
113 | * and the remaining arguments are applied to the
|
---|
114 | * function for access.
|
---|
115 | *
|
---|
116 | * When the `name` is "*" an un-matched command
|
---|
117 | * will be passed as the first arg, followed by
|
---|
118 | * the rest of __ARGV__ remaining.
|
---|
119 | *
|
---|
120 | * Examples:
|
---|
121 | *
|
---|
122 | * program
|
---|
123 | * .version('0.0.1')
|
---|
124 | * .option('-C, --chdir <path>', 'change the working directory')
|
---|
125 | * .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
|
---|
126 | * .option('-T, --no-tests', 'ignore test hook')
|
---|
127 | *
|
---|
128 | * program
|
---|
129 | * .command('setup')
|
---|
130 | * .description('run remote setup commands')
|
---|
131 | * .action(function() {
|
---|
132 | * console.log('setup');
|
---|
133 | * });
|
---|
134 | *
|
---|
135 | * program
|
---|
136 | * .command('exec <cmd>')
|
---|
137 | * .description('run the given remote command')
|
---|
138 | * .action(function(cmd) {
|
---|
139 | * console.log('exec "%s"', cmd);
|
---|
140 | * });
|
---|
141 | *
|
---|
142 | * program
|
---|
143 | * .command('teardown <dir> [otherDirs...]')
|
---|
144 | * .description('run teardown commands')
|
---|
145 | * .action(function(dir, otherDirs) {
|
---|
146 | * console.log('dir "%s"', dir);
|
---|
147 | * if (otherDirs) {
|
---|
148 | * otherDirs.forEach(function (oDir) {
|
---|
149 | * console.log('dir "%s"', oDir);
|
---|
150 | * });
|
---|
151 | * }
|
---|
152 | * });
|
---|
153 | *
|
---|
154 | * program
|
---|
155 | * .command('*')
|
---|
156 | * .description('deploy the given env')
|
---|
157 | * .action(function(env) {
|
---|
158 | * console.log('deploying "%s"', env);
|
---|
159 | * });
|
---|
160 | *
|
---|
161 | * program.parse(process.argv);
|
---|
162 | *
|
---|
163 | * @param {String} name
|
---|
164 | * @param {String} [desc] for git-style sub-commands
|
---|
165 | * @return {Command} the new command
|
---|
166 | * @api public
|
---|
167 | */
|
---|
168 |
|
---|
169 | Command.prototype.command = function(name, desc, opts) {
|
---|
170 | if (typeof desc === 'object' && desc !== null) {
|
---|
171 | opts = desc;
|
---|
172 | desc = null;
|
---|
173 | }
|
---|
174 | opts = opts || {};
|
---|
175 | var args = name.split(/ +/);
|
---|
176 | var cmd = new Command(args.shift());
|
---|
177 |
|
---|
178 | if (desc) {
|
---|
179 | cmd.description(desc);
|
---|
180 | this.executables = true;
|
---|
181 | this._execs[cmd._name] = true;
|
---|
182 | if (opts.isDefault) this.defaultExecutable = cmd._name;
|
---|
183 | }
|
---|
184 | cmd._noHelp = !!opts.noHelp;
|
---|
185 | this.commands.push(cmd);
|
---|
186 | cmd.parseExpectedArgs(args);
|
---|
187 | cmd.parent = this;
|
---|
188 |
|
---|
189 | if (desc) return this;
|
---|
190 | return cmd;
|
---|
191 | };
|
---|
192 |
|
---|
193 | /**
|
---|
194 | * Define argument syntax for the top-level command.
|
---|
195 | *
|
---|
196 | * @api public
|
---|
197 | */
|
---|
198 |
|
---|
199 | Command.prototype.arguments = function(desc) {
|
---|
200 | return this.parseExpectedArgs(desc.split(/ +/));
|
---|
201 | };
|
---|
202 |
|
---|
203 | /**
|
---|
204 | * Add an implicit `help [cmd]` subcommand
|
---|
205 | * which invokes `--help` for the given command.
|
---|
206 | *
|
---|
207 | * @api private
|
---|
208 | */
|
---|
209 |
|
---|
210 | Command.prototype.addImplicitHelpCommand = function() {
|
---|
211 | this.command('help [cmd]', 'display help for [cmd]');
|
---|
212 | };
|
---|
213 |
|
---|
214 | /**
|
---|
215 | * Parse expected `args`.
|
---|
216 | *
|
---|
217 | * For example `["[type]"]` becomes `[{ required: false, name: 'type' }]`.
|
---|
218 | *
|
---|
219 | * @param {Array} args
|
---|
220 | * @return {Command} for chaining
|
---|
221 | * @api public
|
---|
222 | */
|
---|
223 |
|
---|
224 | Command.prototype.parseExpectedArgs = function(args) {
|
---|
225 | if (!args.length) return;
|
---|
226 | var self = this;
|
---|
227 | args.forEach(function(arg) {
|
---|
228 | var argDetails = {
|
---|
229 | required: false,
|
---|
230 | name: '',
|
---|
231 | variadic: false
|
---|
232 | };
|
---|
233 |
|
---|
234 | switch (arg[0]) {
|
---|
235 | case '<':
|
---|
236 | argDetails.required = true;
|
---|
237 | argDetails.name = arg.slice(1, -1);
|
---|
238 | break;
|
---|
239 | case '[':
|
---|
240 | argDetails.name = arg.slice(1, -1);
|
---|
241 | break;
|
---|
242 | }
|
---|
243 |
|
---|
244 | if (argDetails.name.length > 3 && argDetails.name.slice(-3) === '...') {
|
---|
245 | argDetails.variadic = true;
|
---|
246 | argDetails.name = argDetails.name.slice(0, -3);
|
---|
247 | }
|
---|
248 | if (argDetails.name) {
|
---|
249 | self._args.push(argDetails);
|
---|
250 | }
|
---|
251 | });
|
---|
252 | return this;
|
---|
253 | };
|
---|
254 |
|
---|
255 | /**
|
---|
256 | * Register callback `fn` for the command.
|
---|
257 | *
|
---|
258 | * Examples:
|
---|
259 | *
|
---|
260 | * program
|
---|
261 | * .command('help')
|
---|
262 | * .description('display verbose help')
|
---|
263 | * .action(function() {
|
---|
264 | * // output help here
|
---|
265 | * });
|
---|
266 | *
|
---|
267 | * @param {Function} fn
|
---|
268 | * @return {Command} for chaining
|
---|
269 | * @api public
|
---|
270 | */
|
---|
271 |
|
---|
272 | Command.prototype.action = function(fn) {
|
---|
273 | var self = this;
|
---|
274 | var listener = function(args, unknown) {
|
---|
275 | // Parse any so-far unknown options
|
---|
276 | args = args || [];
|
---|
277 | unknown = unknown || [];
|
---|
278 |
|
---|
279 | var parsed = self.parseOptions(unknown);
|
---|
280 |
|
---|
281 | // Output help if necessary
|
---|
282 | outputHelpIfNecessary(self, parsed.unknown);
|
---|
283 |
|
---|
284 | // If there are still any unknown options, then we simply
|
---|
285 | // die, unless someone asked for help, in which case we give it
|
---|
286 | // to them, and then we die.
|
---|
287 | if (parsed.unknown.length > 0) {
|
---|
288 | self.unknownOption(parsed.unknown[0]);
|
---|
289 | }
|
---|
290 |
|
---|
291 | // Leftover arguments need to be pushed back. Fixes issue #56
|
---|
292 | if (parsed.args.length) args = parsed.args.concat(args);
|
---|
293 |
|
---|
294 | self._args.forEach(function(arg, i) {
|
---|
295 | if (arg.required && args[i] == null) {
|
---|
296 | self.missingArgument(arg.name);
|
---|
297 | } else if (arg.variadic) {
|
---|
298 | if (i !== self._args.length - 1) {
|
---|
299 | self.variadicArgNotLast(arg.name);
|
---|
300 | }
|
---|
301 |
|
---|
302 | args[i] = args.splice(i);
|
---|
303 | }
|
---|
304 | });
|
---|
305 |
|
---|
306 | // Always append ourselves to the end of the arguments,
|
---|
307 | // to make sure we match the number of arguments the user
|
---|
308 | // expects
|
---|
309 | if (self._args.length) {
|
---|
310 | args[self._args.length] = self;
|
---|
311 | } else {
|
---|
312 | args.push(self);
|
---|
313 | }
|
---|
314 |
|
---|
315 | fn.apply(self, args);
|
---|
316 | };
|
---|
317 | var parent = this.parent || this;
|
---|
318 | var name = parent === this ? '*' : this._name;
|
---|
319 | parent.on('command:' + name, listener);
|
---|
320 | if (this._alias) parent.on('command:' + this._alias, listener);
|
---|
321 | return this;
|
---|
322 | };
|
---|
323 |
|
---|
324 | /**
|
---|
325 | * Define option with `flags`, `description` and optional
|
---|
326 | * coercion `fn`.
|
---|
327 | *
|
---|
328 | * The `flags` string should contain both the short and long flags,
|
---|
329 | * separated by comma, a pipe or space. The following are all valid
|
---|
330 | * all will output this way when `--help` is used.
|
---|
331 | *
|
---|
332 | * "-p, --pepper"
|
---|
333 | * "-p|--pepper"
|
---|
334 | * "-p --pepper"
|
---|
335 | *
|
---|
336 | * Examples:
|
---|
337 | *
|
---|
338 | * // simple boolean defaulting to false
|
---|
339 | * program.option('-p, --pepper', 'add pepper');
|
---|
340 | *
|
---|
341 | * --pepper
|
---|
342 | * program.pepper
|
---|
343 | * // => Boolean
|
---|
344 | *
|
---|
345 | * // simple boolean defaulting to true
|
---|
346 | * program.option('-C, --no-cheese', 'remove cheese');
|
---|
347 | *
|
---|
348 | * program.cheese
|
---|
349 | * // => true
|
---|
350 | *
|
---|
351 | * --no-cheese
|
---|
352 | * program.cheese
|
---|
353 | * // => false
|
---|
354 | *
|
---|
355 | * // required argument
|
---|
356 | * program.option('-C, --chdir <path>', 'change the working directory');
|
---|
357 | *
|
---|
358 | * --chdir /tmp
|
---|
359 | * program.chdir
|
---|
360 | * // => "/tmp"
|
---|
361 | *
|
---|
362 | * // optional argument
|
---|
363 | * program.option('-c, --cheese [type]', 'add cheese [marble]');
|
---|
364 | *
|
---|
365 | * @param {String} flags
|
---|
366 | * @param {String} description
|
---|
367 | * @param {Function|*} [fn] or default
|
---|
368 | * @param {*} [defaultValue]
|
---|
369 | * @return {Command} for chaining
|
---|
370 | * @api public
|
---|
371 | */
|
---|
372 |
|
---|
373 | Command.prototype.option = function(flags, description, fn, defaultValue) {
|
---|
374 | var self = this,
|
---|
375 | option = new Option(flags, description),
|
---|
376 | oname = option.name(),
|
---|
377 | name = option.attributeName();
|
---|
378 |
|
---|
379 | // default as 3rd arg
|
---|
380 | if (typeof fn !== 'function') {
|
---|
381 | if (fn instanceof RegExp) {
|
---|
382 | var regex = fn;
|
---|
383 | fn = function(val, def) {
|
---|
384 | var m = regex.exec(val);
|
---|
385 | return m ? m[0] : def;
|
---|
386 | };
|
---|
387 | } else {
|
---|
388 | defaultValue = fn;
|
---|
389 | fn = null;
|
---|
390 | }
|
---|
391 | }
|
---|
392 |
|
---|
393 | // preassign default value only for --no-*, [optional], or <required>
|
---|
394 | if (!option.bool || option.optional || option.required) {
|
---|
395 | // when --no-* we make sure default is true
|
---|
396 | if (!option.bool) defaultValue = true;
|
---|
397 | // preassign only if we have a default
|
---|
398 | if (defaultValue !== undefined) {
|
---|
399 | self[name] = defaultValue;
|
---|
400 | option.defaultValue = defaultValue;
|
---|
401 | }
|
---|
402 | }
|
---|
403 |
|
---|
404 | // register the option
|
---|
405 | this.options.push(option);
|
---|
406 |
|
---|
407 | // when it's passed assign the value
|
---|
408 | // and conditionally invoke the callback
|
---|
409 | this.on('option:' + oname, function(val) {
|
---|
410 | // coercion
|
---|
411 | if (val !== null && fn) {
|
---|
412 | val = fn(val, self[name] === undefined ? defaultValue : self[name]);
|
---|
413 | }
|
---|
414 |
|
---|
415 | // unassigned or bool
|
---|
416 | if (typeof self[name] === 'boolean' || typeof self[name] === 'undefined') {
|
---|
417 | // if no value, bool true, and we have a default, then use it!
|
---|
418 | if (val == null) {
|
---|
419 | self[name] = option.bool
|
---|
420 | ? defaultValue || true
|
---|
421 | : false;
|
---|
422 | } else {
|
---|
423 | self[name] = val;
|
---|
424 | }
|
---|
425 | } else if (val !== null) {
|
---|
426 | // reassign
|
---|
427 | self[name] = val;
|
---|
428 | }
|
---|
429 | });
|
---|
430 |
|
---|
431 | return this;
|
---|
432 | };
|
---|
433 |
|
---|
434 | /**
|
---|
435 | * Allow unknown options on the command line.
|
---|
436 | *
|
---|
437 | * @param {Boolean} arg if `true` or omitted, no error will be thrown
|
---|
438 | * for unknown options.
|
---|
439 | * @api public
|
---|
440 | */
|
---|
441 | Command.prototype.allowUnknownOption = function(arg) {
|
---|
442 | this._allowUnknownOption = arguments.length === 0 || arg;
|
---|
443 | return this;
|
---|
444 | };
|
---|
445 |
|
---|
446 | /**
|
---|
447 | * Parse `argv`, settings options and invoking commands when defined.
|
---|
448 | *
|
---|
449 | * @param {Array} argv
|
---|
450 | * @return {Command} for chaining
|
---|
451 | * @api public
|
---|
452 | */
|
---|
453 |
|
---|
454 | Command.prototype.parse = function(argv) {
|
---|
455 | // implicit help
|
---|
456 | if (this.executables) this.addImplicitHelpCommand();
|
---|
457 |
|
---|
458 | // store raw args
|
---|
459 | this.rawArgs = argv;
|
---|
460 |
|
---|
461 | // guess name
|
---|
462 | this._name = this._name || basename(argv[1], '.js');
|
---|
463 |
|
---|
464 | // github-style sub-commands with no sub-command
|
---|
465 | if (this.executables && argv.length < 3 && !this.defaultExecutable) {
|
---|
466 | // this user needs help
|
---|
467 | argv.push('--help');
|
---|
468 | }
|
---|
469 |
|
---|
470 | // process argv
|
---|
471 | var parsed = this.parseOptions(this.normalize(argv.slice(2)));
|
---|
472 | var args = this.args = parsed.args;
|
---|
473 |
|
---|
474 | var result = this.parseArgs(this.args, parsed.unknown);
|
---|
475 |
|
---|
476 | // executable sub-commands
|
---|
477 | var name = result.args[0];
|
---|
478 |
|
---|
479 | var aliasCommand = null;
|
---|
480 | // check alias of sub commands
|
---|
481 | if (name) {
|
---|
482 | aliasCommand = this.commands.filter(function(command) {
|
---|
483 | return command.alias() === name;
|
---|
484 | })[0];
|
---|
485 | }
|
---|
486 |
|
---|
487 | if (this._execs[name] === true) {
|
---|
488 | return this.executeSubCommand(argv, args, parsed.unknown);
|
---|
489 | } else if (aliasCommand) {
|
---|
490 | // is alias of a subCommand
|
---|
491 | args[0] = aliasCommand._name;
|
---|
492 | return this.executeSubCommand(argv, args, parsed.unknown);
|
---|
493 | } else if (this.defaultExecutable) {
|
---|
494 | // use the default subcommand
|
---|
495 | args.unshift(this.defaultExecutable);
|
---|
496 | return this.executeSubCommand(argv, args, parsed.unknown);
|
---|
497 | }
|
---|
498 |
|
---|
499 | return result;
|
---|
500 | };
|
---|
501 |
|
---|
502 | /**
|
---|
503 | * Execute a sub-command executable.
|
---|
504 | *
|
---|
505 | * @param {Array} argv
|
---|
506 | * @param {Array} args
|
---|
507 | * @param {Array} unknown
|
---|
508 | * @api private
|
---|
509 | */
|
---|
510 |
|
---|
511 | Command.prototype.executeSubCommand = function(argv, args, unknown) {
|
---|
512 | args = args.concat(unknown);
|
---|
513 |
|
---|
514 | if (!args.length) this.help();
|
---|
515 | if (args[0] === 'help' && args.length === 1) this.help();
|
---|
516 |
|
---|
517 | // <cmd> --help
|
---|
518 | if (args[0] === 'help') {
|
---|
519 | args[0] = args[1];
|
---|
520 | args[1] = '--help';
|
---|
521 | }
|
---|
522 |
|
---|
523 | // executable
|
---|
524 | var f = argv[1];
|
---|
525 | // name of the subcommand, link `pm-install`
|
---|
526 | var bin = basename(f, path.extname(f)) + '-' + args[0];
|
---|
527 |
|
---|
528 | // In case of globally installed, get the base dir where executable
|
---|
529 | // subcommand file should be located at
|
---|
530 | var baseDir;
|
---|
531 |
|
---|
532 | var resolvedLink = fs.realpathSync(f);
|
---|
533 |
|
---|
534 | baseDir = dirname(resolvedLink);
|
---|
535 |
|
---|
536 | // prefer local `./<bin>` to bin in the $PATH
|
---|
537 | var localBin = path.join(baseDir, bin);
|
---|
538 |
|
---|
539 | // whether bin file is a js script with explicit `.js` or `.ts` extension
|
---|
540 | var isExplicitJS = false;
|
---|
541 | if (exists(localBin + '.js')) {
|
---|
542 | bin = localBin + '.js';
|
---|
543 | isExplicitJS = true;
|
---|
544 | } else if (exists(localBin + '.ts')) {
|
---|
545 | bin = localBin + '.ts';
|
---|
546 | isExplicitJS = true;
|
---|
547 | } else if (exists(localBin)) {
|
---|
548 | bin = localBin;
|
---|
549 | }
|
---|
550 |
|
---|
551 | args = args.slice(1);
|
---|
552 |
|
---|
553 | var proc;
|
---|
554 | if (process.platform !== 'win32') {
|
---|
555 | if (isExplicitJS) {
|
---|
556 | args.unshift(bin);
|
---|
557 | // add executable arguments to spawn
|
---|
558 | args = (process.execArgv || []).concat(args);
|
---|
559 |
|
---|
560 | proc = spawn(process.argv[0], args, { stdio: 'inherit', customFds: [0, 1, 2] });
|
---|
561 | } else {
|
---|
562 | proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });
|
---|
563 | }
|
---|
564 | } else {
|
---|
565 | args.unshift(bin);
|
---|
566 | proc = spawn(process.execPath, args, { stdio: 'inherit' });
|
---|
567 | }
|
---|
568 |
|
---|
569 | var signals = ['SIGUSR1', 'SIGUSR2', 'SIGTERM', 'SIGINT', 'SIGHUP'];
|
---|
570 | signals.forEach(function(signal) {
|
---|
571 | process.on(signal, function() {
|
---|
572 | if (proc.killed === false && proc.exitCode === null) {
|
---|
573 | proc.kill(signal);
|
---|
574 | }
|
---|
575 | });
|
---|
576 | });
|
---|
577 | proc.on('close', process.exit.bind(process));
|
---|
578 | proc.on('error', function(err) {
|
---|
579 | if (err.code === 'ENOENT') {
|
---|
580 | console.error('error: %s(1) does not exist, try --help', bin);
|
---|
581 | } else if (err.code === 'EACCES') {
|
---|
582 | console.error('error: %s(1) not executable. try chmod or run with root', bin);
|
---|
583 | }
|
---|
584 | process.exit(1);
|
---|
585 | });
|
---|
586 |
|
---|
587 | // Store the reference to the child process
|
---|
588 | this.runningCommand = proc;
|
---|
589 | };
|
---|
590 |
|
---|
591 | /**
|
---|
592 | * Normalize `args`, splitting joined short flags. For example
|
---|
593 | * the arg "-abc" is equivalent to "-a -b -c".
|
---|
594 | * This also normalizes equal sign and splits "--abc=def" into "--abc def".
|
---|
595 | *
|
---|
596 | * @param {Array} args
|
---|
597 | * @return {Array}
|
---|
598 | * @api private
|
---|
599 | */
|
---|
600 |
|
---|
601 | Command.prototype.normalize = function(args) {
|
---|
602 | var ret = [],
|
---|
603 | arg,
|
---|
604 | lastOpt,
|
---|
605 | index;
|
---|
606 |
|
---|
607 | for (var i = 0, len = args.length; i < len; ++i) {
|
---|
608 | arg = args[i];
|
---|
609 | if (i > 0) {
|
---|
610 | lastOpt = this.optionFor(args[i - 1]);
|
---|
611 | }
|
---|
612 |
|
---|
613 | if (arg === '--') {
|
---|
614 | // Honor option terminator
|
---|
615 | ret = ret.concat(args.slice(i));
|
---|
616 | break;
|
---|
617 | } else if (lastOpt && lastOpt.required) {
|
---|
618 | ret.push(arg);
|
---|
619 | } else if (arg.length > 1 && arg[0] === '-' && arg[1] !== '-') {
|
---|
620 | arg.slice(1).split('').forEach(function(c) {
|
---|
621 | ret.push('-' + c);
|
---|
622 | });
|
---|
623 | } else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
|
---|
624 | ret.push(arg.slice(0, index), arg.slice(index + 1));
|
---|
625 | } else {
|
---|
626 | ret.push(arg);
|
---|
627 | }
|
---|
628 | }
|
---|
629 |
|
---|
630 | return ret;
|
---|
631 | };
|
---|
632 |
|
---|
633 | /**
|
---|
634 | * Parse command `args`.
|
---|
635 | *
|
---|
636 | * When listener(s) are available those
|
---|
637 | * callbacks are invoked, otherwise the "*"
|
---|
638 | * event is emitted and those actions are invoked.
|
---|
639 | *
|
---|
640 | * @param {Array} args
|
---|
641 | * @return {Command} for chaining
|
---|
642 | * @api private
|
---|
643 | */
|
---|
644 |
|
---|
645 | Command.prototype.parseArgs = function(args, unknown) {
|
---|
646 | var name;
|
---|
647 |
|
---|
648 | if (args.length) {
|
---|
649 | name = args[0];
|
---|
650 | if (this.listeners('command:' + name).length) {
|
---|
651 | this.emit('command:' + args.shift(), args, unknown);
|
---|
652 | } else {
|
---|
653 | this.emit('command:*', args);
|
---|
654 | }
|
---|
655 | } else {
|
---|
656 | outputHelpIfNecessary(this, unknown);
|
---|
657 |
|
---|
658 | // If there were no args and we have unknown options,
|
---|
659 | // then they are extraneous and we need to error.
|
---|
660 | if (unknown.length > 0) {
|
---|
661 | this.unknownOption(unknown[0]);
|
---|
662 | }
|
---|
663 | if (this.commands.length === 0 &&
|
---|
664 | this._args.filter(function(a) { return a.required; }).length === 0) {
|
---|
665 | this.emit('command:*');
|
---|
666 | }
|
---|
667 | }
|
---|
668 |
|
---|
669 | return this;
|
---|
670 | };
|
---|
671 |
|
---|
672 | /**
|
---|
673 | * Return an option matching `arg` if any.
|
---|
674 | *
|
---|
675 | * @param {String} arg
|
---|
676 | * @return {Option}
|
---|
677 | * @api private
|
---|
678 | */
|
---|
679 |
|
---|
680 | Command.prototype.optionFor = function(arg) {
|
---|
681 | for (var i = 0, len = this.options.length; i < len; ++i) {
|
---|
682 | if (this.options[i].is(arg)) {
|
---|
683 | return this.options[i];
|
---|
684 | }
|
---|
685 | }
|
---|
686 | };
|
---|
687 |
|
---|
688 | /**
|
---|
689 | * Parse options from `argv` returning `argv`
|
---|
690 | * void of these options.
|
---|
691 | *
|
---|
692 | * @param {Array} argv
|
---|
693 | * @return {Array}
|
---|
694 | * @api public
|
---|
695 | */
|
---|
696 |
|
---|
697 | Command.prototype.parseOptions = function(argv) {
|
---|
698 | var args = [],
|
---|
699 | len = argv.length,
|
---|
700 | literal,
|
---|
701 | option,
|
---|
702 | arg;
|
---|
703 |
|
---|
704 | var unknownOptions = [];
|
---|
705 |
|
---|
706 | // parse options
|
---|
707 | for (var i = 0; i < len; ++i) {
|
---|
708 | arg = argv[i];
|
---|
709 |
|
---|
710 | // literal args after --
|
---|
711 | if (literal) {
|
---|
712 | args.push(arg);
|
---|
713 | continue;
|
---|
714 | }
|
---|
715 |
|
---|
716 | if (arg === '--') {
|
---|
717 | literal = true;
|
---|
718 | continue;
|
---|
719 | }
|
---|
720 |
|
---|
721 | // find matching Option
|
---|
722 | option = this.optionFor(arg);
|
---|
723 |
|
---|
724 | // option is defined
|
---|
725 | if (option) {
|
---|
726 | // requires arg
|
---|
727 | if (option.required) {
|
---|
728 | arg = argv[++i];
|
---|
729 | if (arg == null) return this.optionMissingArgument(option);
|
---|
730 | this.emit('option:' + option.name(), arg);
|
---|
731 | // optional arg
|
---|
732 | } else if (option.optional) {
|
---|
733 | arg = argv[i + 1];
|
---|
734 | if (arg == null || (arg[0] === '-' && arg !== '-')) {
|
---|
735 | arg = null;
|
---|
736 | } else {
|
---|
737 | ++i;
|
---|
738 | }
|
---|
739 | this.emit('option:' + option.name(), arg);
|
---|
740 | // bool
|
---|
741 | } else {
|
---|
742 | this.emit('option:' + option.name());
|
---|
743 | }
|
---|
744 | continue;
|
---|
745 | }
|
---|
746 |
|
---|
747 | // looks like an option
|
---|
748 | if (arg.length > 1 && arg[0] === '-') {
|
---|
749 | unknownOptions.push(arg);
|
---|
750 |
|
---|
751 | // If the next argument looks like it might be
|
---|
752 | // an argument for this option, we pass it on.
|
---|
753 | // If it isn't, then it'll simply be ignored
|
---|
754 | if ((i + 1) < argv.length && argv[i + 1][0] !== '-') {
|
---|
755 | unknownOptions.push(argv[++i]);
|
---|
756 | }
|
---|
757 | continue;
|
---|
758 | }
|
---|
759 |
|
---|
760 | // arg
|
---|
761 | args.push(arg);
|
---|
762 | }
|
---|
763 |
|
---|
764 | return { args: args, unknown: unknownOptions };
|
---|
765 | };
|
---|
766 |
|
---|
767 | /**
|
---|
768 | * Return an object containing options as key-value pairs
|
---|
769 | *
|
---|
770 | * @return {Object}
|
---|
771 | * @api public
|
---|
772 | */
|
---|
773 | Command.prototype.opts = function() {
|
---|
774 | var result = {},
|
---|
775 | len = this.options.length;
|
---|
776 |
|
---|
777 | for (var i = 0; i < len; i++) {
|
---|
778 | var key = this.options[i].attributeName();
|
---|
779 | result[key] = key === this._versionOptionName ? this._version : this[key];
|
---|
780 | }
|
---|
781 | return result;
|
---|
782 | };
|
---|
783 |
|
---|
784 | /**
|
---|
785 | * Argument `name` is missing.
|
---|
786 | *
|
---|
787 | * @param {String} name
|
---|
788 | * @api private
|
---|
789 | */
|
---|
790 |
|
---|
791 | Command.prototype.missingArgument = function(name) {
|
---|
792 | console.error("error: missing required argument `%s'", name);
|
---|
793 | process.exit(1);
|
---|
794 | };
|
---|
795 |
|
---|
796 | /**
|
---|
797 | * `Option` is missing an argument, but received `flag` or nothing.
|
---|
798 | *
|
---|
799 | * @param {String} option
|
---|
800 | * @param {String} flag
|
---|
801 | * @api private
|
---|
802 | */
|
---|
803 |
|
---|
804 | Command.prototype.optionMissingArgument = function(option, flag) {
|
---|
805 | if (flag) {
|
---|
806 | console.error("error: option `%s' argument missing, got `%s'", option.flags, flag);
|
---|
807 | } else {
|
---|
808 | console.error("error: option `%s' argument missing", option.flags);
|
---|
809 | }
|
---|
810 | process.exit(1);
|
---|
811 | };
|
---|
812 |
|
---|
813 | /**
|
---|
814 | * Unknown option `flag`.
|
---|
815 | *
|
---|
816 | * @param {String} flag
|
---|
817 | * @api private
|
---|
818 | */
|
---|
819 |
|
---|
820 | Command.prototype.unknownOption = function(flag) {
|
---|
821 | if (this._allowUnknownOption) return;
|
---|
822 | console.error("error: unknown option `%s'", flag);
|
---|
823 | process.exit(1);
|
---|
824 | };
|
---|
825 |
|
---|
826 | /**
|
---|
827 | * Variadic argument with `name` is not the last argument as required.
|
---|
828 | *
|
---|
829 | * @param {String} name
|
---|
830 | * @api private
|
---|
831 | */
|
---|
832 |
|
---|
833 | Command.prototype.variadicArgNotLast = function(name) {
|
---|
834 | console.error("error: variadic arguments must be last `%s'", name);
|
---|
835 | process.exit(1);
|
---|
836 | };
|
---|
837 |
|
---|
838 | /**
|
---|
839 | * Set the program version to `str`.
|
---|
840 | *
|
---|
841 | * This method auto-registers the "-V, --version" flag
|
---|
842 | * which will print the version number when passed.
|
---|
843 | *
|
---|
844 | * @param {String} str
|
---|
845 | * @param {String} [flags]
|
---|
846 | * @return {Command} for chaining
|
---|
847 | * @api public
|
---|
848 | */
|
---|
849 |
|
---|
850 | Command.prototype.version = function(str, flags) {
|
---|
851 | if (arguments.length === 0) return this._version;
|
---|
852 | this._version = str;
|
---|
853 | flags = flags || '-V, --version';
|
---|
854 | var versionOption = new Option(flags, 'output the version number');
|
---|
855 | this._versionOptionName = versionOption.long.substr(2) || 'version';
|
---|
856 | this.options.push(versionOption);
|
---|
857 | this.on('option:' + this._versionOptionName, function() {
|
---|
858 | process.stdout.write(str + '\n');
|
---|
859 | process.exit(0);
|
---|
860 | });
|
---|
861 | return this;
|
---|
862 | };
|
---|
863 |
|
---|
864 | /**
|
---|
865 | * Set the description to `str`.
|
---|
866 | *
|
---|
867 | * @param {String} str
|
---|
868 | * @param {Object} argsDescription
|
---|
869 | * @return {String|Command}
|
---|
870 | * @api public
|
---|
871 | */
|
---|
872 |
|
---|
873 | Command.prototype.description = function(str, argsDescription) {
|
---|
874 | if (arguments.length === 0) return this._description;
|
---|
875 | this._description = str;
|
---|
876 | this._argsDescription = argsDescription;
|
---|
877 | return this;
|
---|
878 | };
|
---|
879 |
|
---|
880 | /**
|
---|
881 | * Set an alias for the command
|
---|
882 | *
|
---|
883 | * @param {String} alias
|
---|
884 | * @return {String|Command}
|
---|
885 | * @api public
|
---|
886 | */
|
---|
887 |
|
---|
888 | Command.prototype.alias = function(alias) {
|
---|
889 | var command = this;
|
---|
890 | if (this.commands.length !== 0) {
|
---|
891 | command = this.commands[this.commands.length - 1];
|
---|
892 | }
|
---|
893 |
|
---|
894 | if (arguments.length === 0) return command._alias;
|
---|
895 |
|
---|
896 | if (alias === command._name) throw new Error('Command alias can\'t be the same as its name');
|
---|
897 |
|
---|
898 | command._alias = alias;
|
---|
899 | return this;
|
---|
900 | };
|
---|
901 |
|
---|
902 | /**
|
---|
903 | * Set / get the command usage `str`.
|
---|
904 | *
|
---|
905 | * @param {String} str
|
---|
906 | * @return {String|Command}
|
---|
907 | * @api public
|
---|
908 | */
|
---|
909 |
|
---|
910 | Command.prototype.usage = function(str) {
|
---|
911 | var args = this._args.map(function(arg) {
|
---|
912 | return humanReadableArgName(arg);
|
---|
913 | });
|
---|
914 |
|
---|
915 | var usage = '[options]' +
|
---|
916 | (this.commands.length ? ' [command]' : '') +
|
---|
917 | (this._args.length ? ' ' + args.join(' ') : '');
|
---|
918 |
|
---|
919 | if (arguments.length === 0) return this._usage || usage;
|
---|
920 | this._usage = str;
|
---|
921 |
|
---|
922 | return this;
|
---|
923 | };
|
---|
924 |
|
---|
925 | /**
|
---|
926 | * Get or set the name of the command
|
---|
927 | *
|
---|
928 | * @param {String} str
|
---|
929 | * @return {String|Command}
|
---|
930 | * @api public
|
---|
931 | */
|
---|
932 |
|
---|
933 | Command.prototype.name = function(str) {
|
---|
934 | if (arguments.length === 0) return this._name;
|
---|
935 | this._name = str;
|
---|
936 | return this;
|
---|
937 | };
|
---|
938 |
|
---|
939 | /**
|
---|
940 | * Return prepared commands.
|
---|
941 | *
|
---|
942 | * @return {Array}
|
---|
943 | * @api private
|
---|
944 | */
|
---|
945 |
|
---|
946 | Command.prototype.prepareCommands = function() {
|
---|
947 | return this.commands.filter(function(cmd) {
|
---|
948 | return !cmd._noHelp;
|
---|
949 | }).map(function(cmd) {
|
---|
950 | var args = cmd._args.map(function(arg) {
|
---|
951 | return humanReadableArgName(arg);
|
---|
952 | }).join(' ');
|
---|
953 |
|
---|
954 | return [
|
---|
955 | cmd._name +
|
---|
956 | (cmd._alias ? '|' + cmd._alias : '') +
|
---|
957 | (cmd.options.length ? ' [options]' : '') +
|
---|
958 | (args ? ' ' + args : ''),
|
---|
959 | cmd._description
|
---|
960 | ];
|
---|
961 | });
|
---|
962 | };
|
---|
963 |
|
---|
964 | /**
|
---|
965 | * Return the largest command length.
|
---|
966 | *
|
---|
967 | * @return {Number}
|
---|
968 | * @api private
|
---|
969 | */
|
---|
970 |
|
---|
971 | Command.prototype.largestCommandLength = function() {
|
---|
972 | var commands = this.prepareCommands();
|
---|
973 | return commands.reduce(function(max, command) {
|
---|
974 | return Math.max(max, command[0].length);
|
---|
975 | }, 0);
|
---|
976 | };
|
---|
977 |
|
---|
978 | /**
|
---|
979 | * Return the largest option length.
|
---|
980 | *
|
---|
981 | * @return {Number}
|
---|
982 | * @api private
|
---|
983 | */
|
---|
984 |
|
---|
985 | Command.prototype.largestOptionLength = function() {
|
---|
986 | var options = [].slice.call(this.options);
|
---|
987 | options.push({
|
---|
988 | flags: '-h, --help'
|
---|
989 | });
|
---|
990 | return options.reduce(function(max, option) {
|
---|
991 | return Math.max(max, option.flags.length);
|
---|
992 | }, 0);
|
---|
993 | };
|
---|
994 |
|
---|
995 | /**
|
---|
996 | * Return the largest arg length.
|
---|
997 | *
|
---|
998 | * @return {Number}
|
---|
999 | * @api private
|
---|
1000 | */
|
---|
1001 |
|
---|
1002 | Command.prototype.largestArgLength = function() {
|
---|
1003 | return this._args.reduce(function(max, arg) {
|
---|
1004 | return Math.max(max, arg.name.length);
|
---|
1005 | }, 0);
|
---|
1006 | };
|
---|
1007 |
|
---|
1008 | /**
|
---|
1009 | * Return the pad width.
|
---|
1010 | *
|
---|
1011 | * @return {Number}
|
---|
1012 | * @api private
|
---|
1013 | */
|
---|
1014 |
|
---|
1015 | Command.prototype.padWidth = function() {
|
---|
1016 | var width = this.largestOptionLength();
|
---|
1017 | if (this._argsDescription && this._args.length) {
|
---|
1018 | if (this.largestArgLength() > width) {
|
---|
1019 | width = this.largestArgLength();
|
---|
1020 | }
|
---|
1021 | }
|
---|
1022 |
|
---|
1023 | if (this.commands && this.commands.length) {
|
---|
1024 | if (this.largestCommandLength() > width) {
|
---|
1025 | width = this.largestCommandLength();
|
---|
1026 | }
|
---|
1027 | }
|
---|
1028 |
|
---|
1029 | return width;
|
---|
1030 | };
|
---|
1031 |
|
---|
1032 | /**
|
---|
1033 | * Return help for options.
|
---|
1034 | *
|
---|
1035 | * @return {String}
|
---|
1036 | * @api private
|
---|
1037 | */
|
---|
1038 |
|
---|
1039 | Command.prototype.optionHelp = function() {
|
---|
1040 | var width = this.padWidth();
|
---|
1041 |
|
---|
1042 | // Append the help information
|
---|
1043 | return this.options.map(function(option) {
|
---|
1044 | return pad(option.flags, width) + ' ' + option.description +
|
---|
1045 | ((option.bool && option.defaultValue !== undefined) ? ' (default: ' + JSON.stringify(option.defaultValue) + ')' : '');
|
---|
1046 | }).concat([pad('-h, --help', width) + ' ' + 'output usage information'])
|
---|
1047 | .join('\n');
|
---|
1048 | };
|
---|
1049 |
|
---|
1050 | /**
|
---|
1051 | * Return command help documentation.
|
---|
1052 | *
|
---|
1053 | * @return {String}
|
---|
1054 | * @api private
|
---|
1055 | */
|
---|
1056 |
|
---|
1057 | Command.prototype.commandHelp = function() {
|
---|
1058 | if (!this.commands.length) return '';
|
---|
1059 |
|
---|
1060 | var commands = this.prepareCommands();
|
---|
1061 | var width = this.padWidth();
|
---|
1062 |
|
---|
1063 | return [
|
---|
1064 | 'Commands:',
|
---|
1065 | commands.map(function(cmd) {
|
---|
1066 | var desc = cmd[1] ? ' ' + cmd[1] : '';
|
---|
1067 | return (desc ? pad(cmd[0], width) : cmd[0]) + desc;
|
---|
1068 | }).join('\n').replace(/^/gm, ' '),
|
---|
1069 | ''
|
---|
1070 | ].join('\n');
|
---|
1071 | };
|
---|
1072 |
|
---|
1073 | /**
|
---|
1074 | * Return program help documentation.
|
---|
1075 | *
|
---|
1076 | * @return {String}
|
---|
1077 | * @api private
|
---|
1078 | */
|
---|
1079 |
|
---|
1080 | Command.prototype.helpInformation = function() {
|
---|
1081 | var desc = [];
|
---|
1082 | if (this._description) {
|
---|
1083 | desc = [
|
---|
1084 | this._description,
|
---|
1085 | ''
|
---|
1086 | ];
|
---|
1087 |
|
---|
1088 | var argsDescription = this._argsDescription;
|
---|
1089 | if (argsDescription && this._args.length) {
|
---|
1090 | var width = this.padWidth();
|
---|
1091 | desc.push('Arguments:');
|
---|
1092 | desc.push('');
|
---|
1093 | this._args.forEach(function(arg) {
|
---|
1094 | desc.push(' ' + pad(arg.name, width) + ' ' + argsDescription[arg.name]);
|
---|
1095 | });
|
---|
1096 | desc.push('');
|
---|
1097 | }
|
---|
1098 | }
|
---|
1099 |
|
---|
1100 | var cmdName = this._name;
|
---|
1101 | if (this._alias) {
|
---|
1102 | cmdName = cmdName + '|' + this._alias;
|
---|
1103 | }
|
---|
1104 | var usage = [
|
---|
1105 | 'Usage: ' + cmdName + ' ' + this.usage(),
|
---|
1106 | ''
|
---|
1107 | ];
|
---|
1108 |
|
---|
1109 | var cmds = [];
|
---|
1110 | var commandHelp = this.commandHelp();
|
---|
1111 | if (commandHelp) cmds = [commandHelp];
|
---|
1112 |
|
---|
1113 | var options = [
|
---|
1114 | 'Options:',
|
---|
1115 | '' + this.optionHelp().replace(/^/gm, ' '),
|
---|
1116 | ''
|
---|
1117 | ];
|
---|
1118 |
|
---|
1119 | return usage
|
---|
1120 | .concat(desc)
|
---|
1121 | .concat(options)
|
---|
1122 | .concat(cmds)
|
---|
1123 | .join('\n');
|
---|
1124 | };
|
---|
1125 |
|
---|
1126 | /**
|
---|
1127 | * Output help information for this command
|
---|
1128 | *
|
---|
1129 | * @api public
|
---|
1130 | */
|
---|
1131 |
|
---|
1132 | Command.prototype.outputHelp = function(cb) {
|
---|
1133 | if (!cb) {
|
---|
1134 | cb = function(passthru) {
|
---|
1135 | return passthru;
|
---|
1136 | };
|
---|
1137 | }
|
---|
1138 | process.stdout.write(cb(this.helpInformation()));
|
---|
1139 | this.emit('--help');
|
---|
1140 | };
|
---|
1141 |
|
---|
1142 | /**
|
---|
1143 | * Output help information and exit.
|
---|
1144 | *
|
---|
1145 | * @api public
|
---|
1146 | */
|
---|
1147 |
|
---|
1148 | Command.prototype.help = function(cb) {
|
---|
1149 | this.outputHelp(cb);
|
---|
1150 | process.exit();
|
---|
1151 | };
|
---|
1152 |
|
---|
1153 | /**
|
---|
1154 | * Camel-case the given `flag`
|
---|
1155 | *
|
---|
1156 | * @param {String} flag
|
---|
1157 | * @return {String}
|
---|
1158 | * @api private
|
---|
1159 | */
|
---|
1160 |
|
---|
1161 | function camelcase(flag) {
|
---|
1162 | return flag.split('-').reduce(function(str, word) {
|
---|
1163 | return str + word[0].toUpperCase() + word.slice(1);
|
---|
1164 | });
|
---|
1165 | }
|
---|
1166 |
|
---|
1167 | /**
|
---|
1168 | * Pad `str` to `width`.
|
---|
1169 | *
|
---|
1170 | * @param {String} str
|
---|
1171 | * @param {Number} width
|
---|
1172 | * @return {String}
|
---|
1173 | * @api private
|
---|
1174 | */
|
---|
1175 |
|
---|
1176 | function pad(str, width) {
|
---|
1177 | var len = Math.max(0, width - str.length);
|
---|
1178 | return str + Array(len + 1).join(' ');
|
---|
1179 | }
|
---|
1180 |
|
---|
1181 | /**
|
---|
1182 | * Output help information if necessary
|
---|
1183 | *
|
---|
1184 | * @param {Command} command to output help for
|
---|
1185 | * @param {Array} array of options to search for -h or --help
|
---|
1186 | * @api private
|
---|
1187 | */
|
---|
1188 |
|
---|
1189 | function outputHelpIfNecessary(cmd, options) {
|
---|
1190 | options = options || [];
|
---|
1191 | for (var i = 0; i < options.length; i++) {
|
---|
1192 | if (options[i] === '--help' || options[i] === '-h') {
|
---|
1193 | cmd.outputHelp();
|
---|
1194 | process.exit(0);
|
---|
1195 | }
|
---|
1196 | }
|
---|
1197 | }
|
---|
1198 |
|
---|
1199 | /**
|
---|
1200 | * Takes an argument an returns its human readable equivalent for help usage.
|
---|
1201 | *
|
---|
1202 | * @param {Object} arg
|
---|
1203 | * @return {String}
|
---|
1204 | * @api private
|
---|
1205 | */
|
---|
1206 |
|
---|
1207 | function humanReadableArgName(arg) {
|
---|
1208 | var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
|
---|
1209 |
|
---|
1210 | return arg.required
|
---|
1211 | ? '<' + nameOutput + '>'
|
---|
1212 | : '[' + nameOutput + ']';
|
---|
1213 | }
|
---|
1214 |
|
---|
1215 | // for versions before node v0.8 when there weren't `fs.existsSync`
|
---|
1216 | function exists(file) {
|
---|
1217 | try {
|
---|
1218 | if (fs.statSync(file).isFile()) {
|
---|
1219 | return true;
|
---|
1220 | }
|
---|
1221 | } catch (e) {
|
---|
1222 | return false;
|
---|
1223 | }
|
---|
1224 | }
|
---|