Command.executeSubCommand   F
last analyzed

Complexity

Conditions 12
Paths 384

Size

Total Lines 80

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 12
nc 384
dl 0
loc 80
rs 3.8193
c 0
b 0
f 0
nop 3

2 Functions

Rating   Name   Duplication   Size   Complexity  
A 0 8 3
A 0 7 1

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

Complexity

Complex classes like Command.executeSubCommand often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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