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