Projekt

Obecné

Profil

Stáhnout (27.2 KB) Statistiky
| Větev: | Revize:
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
}
(4-4/5)