Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Avatar for KuCalc : devops.
Download
50640 views
1
/**
2
* Module dependencies.
3
*/
4
5
var EventEmitter = require('events').EventEmitter;
6
var spawn = require('child_process').spawn;
7
var readlink = require('graceful-readlink').readlinkSync;
8
var path = require('path');
9
var dirname = path.dirname;
10
var basename = path.basename;
11
var fs = require('fs');
12
13
/**
14
* Expose the root command.
15
*/
16
17
exports = module.exports = new Command();
18
19
/**
20
* Expose `Command`.
21
*/
22
23
exports.Command = Command;
24
25
/**
26
* Expose `Option`.
27
*/
28
29
exports.Option = Option;
30
31
/**
32
* Initialize a new `Option` with the given `flags` and `description`.
33
*
34
* @param {String} flags
35
* @param {String} description
36
* @api public
37
*/
38
39
function Option(flags, description) {
40
this.flags = flags;
41
this.required = ~flags.indexOf('<');
42
this.optional = ~flags.indexOf('[');
43
this.bool = !~flags.indexOf('-no-');
44
flags = flags.split(/[ ,|]+/);
45
if (flags.length > 1 && !/^[[<]/.test(flags[1])) this.short = flags.shift();
46
this.long = flags.shift();
47
this.description = description || '';
48
}
49
50
/**
51
* Return option name.
52
*
53
* @return {String}
54
* @api private
55
*/
56
57
Option.prototype.name = function() {
58
return this.long
59
.replace('--', '')
60
.replace('no-', '');
61
};
62
63
/**
64
* Check if `arg` matches the short or long flag.
65
*
66
* @param {String} arg
67
* @return {Boolean}
68
* @api private
69
*/
70
71
Option.prototype.is = function(arg) {
72
return arg == this.short || arg == this.long;
73
};
74
75
/**
76
* Initialize a new `Command`.
77
*
78
* @param {String} name
79
* @api public
80
*/
81
82
function Command(name) {
83
this.commands = [];
84
this.options = [];
85
this._execs = {};
86
this._allowUnknownOption = false;
87
this._args = [];
88
this._name = name || '';
89
}
90
91
/**
92
* Inherit from `EventEmitter.prototype`.
93
*/
94
95
Command.prototype.__proto__ = EventEmitter.prototype;
96
97
/**
98
* Add command `name`.
99
*
100
* The `.action()` callback is invoked when the
101
* command `name` is specified via __ARGV__,
102
* and the remaining arguments are applied to the
103
* function for access.
104
*
105
* When the `name` is "*" an un-matched command
106
* will be passed as the first arg, followed by
107
* the rest of __ARGV__ remaining.
108
*
109
* Examples:
110
*
111
* program
112
* .version('0.0.1')
113
* .option('-C, --chdir <path>', 'change the working directory')
114
* .option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
115
* .option('-T, --no-tests', 'ignore test hook')
116
*
117
* program
118
* .command('setup')
119
* .description('run remote setup commands')
120
* .action(function() {
121
* console.log('setup');
122
* });
123
*
124
* program
125
* .command('exec <cmd>')
126
* .description('run the given remote command')
127
* .action(function(cmd) {
128
* console.log('exec "%s"', cmd);
129
* });
130
*
131
* program
132
* .command('teardown <dir> [otherDirs...]')
133
* .description('run teardown commands')
134
* .action(function(dir, otherDirs) {
135
* console.log('dir "%s"', dir);
136
* if (otherDirs) {
137
* otherDirs.forEach(function (oDir) {
138
* console.log('dir "%s"', oDir);
139
* });
140
* }
141
* });
142
*
143
* program
144
* .command('*')
145
* .description('deploy the given env')
146
* .action(function(env) {
147
* console.log('deploying "%s"', env);
148
* });
149
*
150
* program.parse(process.argv);
151
*
152
* @param {String} name
153
* @param {String} [desc] for git-style sub-commands
154
* @return {Command} the new command
155
* @api public
156
*/
157
158
Command.prototype.command = function(name, desc, opts) {
159
opts = opts || {};
160
var args = name.split(/ +/);
161
var cmd = new Command(args.shift());
162
163
if (desc) {
164
cmd.description(desc);
165
this.executables = true;
166
this._execs[cmd._name] = true;
167
if (opts.isDefault) this.defaultExecutable = cmd._name;
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|*} [fn] or default
354
* @param {*} [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 && !this.defaultExecutable) {
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
463
var aliasCommand = null;
464
// check alias of sub commands
465
if (name) {
466
aliasCommand = this.commands.filter(function(command) {
467
return command.alias() === name;
468
})[0];
469
}
470
471
if (this._execs[name] && typeof this._execs[name] != "function") {
472
return this.executeSubCommand(argv, args, parsed.unknown);
473
} else if (aliasCommand) {
474
// is alias of a subCommand
475
args[0] = aliasCommand._name;
476
return this.executeSubCommand(argv, args, parsed.unknown);
477
} else if (this.defaultExecutable) {
478
// use the default subcommand
479
args.unshift(this.defaultExecutable);
480
return this.executeSubCommand(argv, args, parsed.unknown);
481
}
482
483
return result;
484
};
485
486
/**
487
* Execute a sub-command executable.
488
*
489
* @param {Array} argv
490
* @param {Array} args
491
* @param {Array} unknown
492
* @api private
493
*/
494
495
Command.prototype.executeSubCommand = function(argv, args, unknown) {
496
args = args.concat(unknown);
497
498
if (!args.length) this.help();
499
if ('help' == args[0] && 1 == args.length) this.help();
500
501
// <cmd> --help
502
if ('help' == args[0]) {
503
args[0] = args[1];
504
args[1] = '--help';
505
}
506
507
// executable
508
var f = argv[1];
509
// name of the subcommand, link `pm-install`
510
var bin = basename(f, '.js') + '-' + args[0];
511
512
513
// In case of globally installed, get the base dir where executable
514
// subcommand file should be located at
515
var baseDir
516
, link = readlink(f);
517
518
// when symbolink is relative path
519
if (link !== f && link.charAt(0) !== '/') {
520
link = path.join(dirname(f), link)
521
}
522
baseDir = dirname(link);
523
524
// prefer local `./<bin>` to bin in the $PATH
525
var localBin = path.join(baseDir, bin);
526
527
// whether bin file is a js script with explicit `.js` extension
528
var isExplicitJS = false;
529
if (exists(localBin + '.js')) {
530
bin = localBin + '.js';
531
isExplicitJS = true;
532
} else if (exists(localBin)) {
533
bin = localBin;
534
}
535
536
args = args.slice(1);
537
538
var proc;
539
if (process.platform !== 'win32') {
540
if (isExplicitJS) {
541
args.unshift(bin);
542
// add executable arguments to spawn
543
args = (process.execArgv || []).concat(args);
544
545
proc = spawn('node', args, { stdio: 'inherit', customFds: [0, 1, 2] });
546
} else {
547
proc = spawn(bin, args, { stdio: 'inherit', customFds: [0, 1, 2] });
548
}
549
} else {
550
args.unshift(bin);
551
proc = spawn(process.execPath, args, { stdio: 'inherit'});
552
}
553
554
proc.on('close', process.exit.bind(process));
555
proc.on('error', function(err) {
556
if (err.code == "ENOENT") {
557
console.error('\n %s(1) does not exist, try --help\n', bin);
558
} else if (err.code == "EACCES") {
559
console.error('\n %s(1) not executable. try chmod or run with root\n', bin);
560
}
561
process.exit(1);
562
});
563
564
// Store the reference to the child process
565
this.runningCommand = proc;
566
};
567
568
/**
569
* Normalize `args`, splitting joined short flags. For example
570
* the arg "-abc" is equivalent to "-a -b -c".
571
* This also normalizes equal sign and splits "--abc=def" into "--abc def".
572
*
573
* @param {Array} args
574
* @return {Array}
575
* @api private
576
*/
577
578
Command.prototype.normalize = function(args) {
579
var ret = []
580
, arg
581
, lastOpt
582
, index;
583
584
for (var i = 0, len = args.length; i < len; ++i) {
585
arg = args[i];
586
if (i > 0) {
587
lastOpt = this.optionFor(args[i-1]);
588
}
589
590
if (arg === '--') {
591
// Honor option terminator
592
ret = ret.concat(args.slice(i));
593
break;
594
} else if (lastOpt && lastOpt.required) {
595
ret.push(arg);
596
} else if (arg.length > 1 && '-' == arg[0] && '-' != arg[1]) {
597
arg.slice(1).split('').forEach(function(c) {
598
ret.push('-' + c);
599
});
600
} else if (/^--/.test(arg) && ~(index = arg.indexOf('='))) {
601
ret.push(arg.slice(0, index), arg.slice(index + 1));
602
} else {
603
ret.push(arg);
604
}
605
}
606
607
return ret;
608
};
609
610
/**
611
* Parse command `args`.
612
*
613
* When listener(s) are available those
614
* callbacks are invoked, otherwise the "*"
615
* event is emitted and those actions are invoked.
616
*
617
* @param {Array} args
618
* @return {Command} for chaining
619
* @api private
620
*/
621
622
Command.prototype.parseArgs = function(args, unknown) {
623
var name;
624
625
if (args.length) {
626
name = args[0];
627
if (this.listeners(name).length) {
628
this.emit(args.shift(), args, unknown);
629
} else {
630
this.emit('*', args);
631
}
632
} else {
633
outputHelpIfNecessary(this, unknown);
634
635
// If there were no args and we have unknown options,
636
// then they are extraneous and we need to error.
637
if (unknown.length > 0) {
638
this.unknownOption(unknown[0]);
639
}
640
}
641
642
return this;
643
};
644
645
/**
646
* Return an option matching `arg` if any.
647
*
648
* @param {String} arg
649
* @return {Option}
650
* @api private
651
*/
652
653
Command.prototype.optionFor = function(arg) {
654
for (var i = 0, len = this.options.length; i < len; ++i) {
655
if (this.options[i].is(arg)) {
656
return this.options[i];
657
}
658
}
659
};
660
661
/**
662
* Parse options from `argv` returning `argv`
663
* void of these options.
664
*
665
* @param {Array} argv
666
* @return {Array}
667
* @api public
668
*/
669
670
Command.prototype.parseOptions = function(argv) {
671
var args = []
672
, len = argv.length
673
, literal
674
, option
675
, arg;
676
677
var unknownOptions = [];
678
679
// parse options
680
for (var i = 0; i < len; ++i) {
681
arg = argv[i];
682
683
// literal args after --
684
if (literal) {
685
args.push(arg);
686
continue;
687
}
688
689
if ('--' == arg) {
690
literal = true;
691
continue;
692
}
693
694
// find matching Option
695
option = this.optionFor(arg);
696
697
// option is defined
698
if (option) {
699
// requires arg
700
if (option.required) {
701
arg = argv[++i];
702
if (null == arg) return this.optionMissingArgument(option);
703
this.emit(option.name(), arg);
704
// optional arg
705
} else if (option.optional) {
706
arg = argv[i+1];
707
if (null == arg || ('-' == arg[0] && '-' != arg)) {
708
arg = null;
709
} else {
710
++i;
711
}
712
this.emit(option.name(), arg);
713
// bool
714
} else {
715
this.emit(option.name());
716
}
717
continue;
718
}
719
720
// looks like an option
721
if (arg.length > 1 && '-' == arg[0]) {
722
unknownOptions.push(arg);
723
724
// If the next argument looks like it might be
725
// an argument for this option, we pass it on.
726
// If it isn't, then it'll simply be ignored
727
if (argv[i+1] && '-' != argv[i+1][0]) {
728
unknownOptions.push(argv[++i]);
729
}
730
continue;
731
}
732
733
// arg
734
args.push(arg);
735
}
736
737
return { args: args, unknown: unknownOptions };
738
};
739
740
/**
741
* Return an object containing options as key-value pairs
742
*
743
* @return {Object}
744
* @api public
745
*/
746
Command.prototype.opts = function() {
747
var result = {}
748
, len = this.options.length;
749
750
for (var i = 0 ; i < len; i++) {
751
var key = camelcase(this.options[i].name());
752
result[key] = key === 'version' ? this._version : this[key];
753
}
754
return result;
755
};
756
757
/**
758
* Argument `name` is missing.
759
*
760
* @param {String} name
761
* @api private
762
*/
763
764
Command.prototype.missingArgument = function(name) {
765
console.error();
766
console.error(" error: missing required argument `%s'", name);
767
console.error();
768
process.exit(1);
769
};
770
771
/**
772
* `Option` is missing an argument, but received `flag` or nothing.
773
*
774
* @param {String} option
775
* @param {String} flag
776
* @api private
777
*/
778
779
Command.prototype.optionMissingArgument = function(option, flag) {
780
console.error();
781
if (flag) {
782
console.error(" error: option `%s' argument missing, got `%s'", option.flags, flag);
783
} else {
784
console.error(" error: option `%s' argument missing", option.flags);
785
}
786
console.error();
787
process.exit(1);
788
};
789
790
/**
791
* Unknown option `flag`.
792
*
793
* @param {String} flag
794
* @api private
795
*/
796
797
Command.prototype.unknownOption = function(flag) {
798
if (this._allowUnknownOption) return;
799
console.error();
800
console.error(" error: unknown option `%s'", flag);
801
console.error();
802
process.exit(1);
803
};
804
805
/**
806
* Variadic argument with `name` is not the last argument as required.
807
*
808
* @param {String} name
809
* @api private
810
*/
811
812
Command.prototype.variadicArgNotLast = function(name) {
813
console.error();
814
console.error(" error: variadic arguments must be last `%s'", name);
815
console.error();
816
process.exit(1);
817
};
818
819
/**
820
* Set the program version to `str`.
821
*
822
* This method auto-registers the "-V, --version" flag
823
* which will print the version number when passed.
824
*
825
* @param {String} str
826
* @param {String} [flags]
827
* @return {Command} for chaining
828
* @api public
829
*/
830
831
Command.prototype.version = function(str, flags) {
832
if (0 == arguments.length) return this._version;
833
this._version = str;
834
flags = flags || '-V, --version';
835
this.option(flags, 'output the version number');
836
this.on('version', function() {
837
process.stdout.write(str + '\n');
838
process.exit(0);
839
});
840
return this;
841
};
842
843
/**
844
* Set the description to `str`.
845
*
846
* @param {String} str
847
* @return {String|Command}
848
* @api public
849
*/
850
851
Command.prototype.description = function(str) {
852
if (0 === arguments.length) return this._description;
853
this._description = str;
854
return this;
855
};
856
857
/**
858
* Set an alias for the command
859
*
860
* @param {String} alias
861
* @return {String|Command}
862
* @api public
863
*/
864
865
Command.prototype.alias = function(alias) {
866
var command = this;
867
if(this.commands.length !== 0) {
868
command = this.commands[this.commands.length - 1]
869
}
870
871
if (arguments.length === 0) return command._alias;
872
873
command._alias = alias;
874
return this;
875
};
876
877
/**
878
* Set / get the command usage `str`.
879
*
880
* @param {String} str
881
* @return {String|Command}
882
* @api public
883
*/
884
885
Command.prototype.usage = function(str) {
886
var args = this._args.map(function(arg) {
887
return humanReadableArgName(arg);
888
});
889
890
var usage = '[options]'
891
+ (this.commands.length ? ' [command]' : '')
892
+ (this._args.length ? ' ' + args.join(' ') : '');
893
894
if (0 == arguments.length) return this._usage || usage;
895
this._usage = str;
896
897
return this;
898
};
899
900
/**
901
* Get the name of the command
902
*
903
* @param {String} name
904
* @return {String|Command}
905
* @api public
906
*/
907
908
Command.prototype.name = function() {
909
return this._name;
910
};
911
912
/**
913
* Return the largest option length.
914
*
915
* @return {Number}
916
* @api private
917
*/
918
919
Command.prototype.largestOptionLength = function() {
920
return this.options.reduce(function(max, option) {
921
return Math.max(max, option.flags.length);
922
}, 0);
923
};
924
925
/**
926
* Return help for options.
927
*
928
* @return {String}
929
* @api private
930
*/
931
932
Command.prototype.optionHelp = function() {
933
var width = this.largestOptionLength();
934
935
// Prepend the help information
936
return [pad('-h, --help', width) + ' ' + 'output usage information']
937
.concat(this.options.map(function(option) {
938
return pad(option.flags, width) + ' ' + option.description;
939
}))
940
.join('\n');
941
};
942
943
/**
944
* Return command help documentation.
945
*
946
* @return {String}
947
* @api private
948
*/
949
950
Command.prototype.commandHelp = function() {
951
if (!this.commands.length) return '';
952
953
var commands = this.commands.filter(function(cmd) {
954
return !cmd._noHelp;
955
}).map(function(cmd) {
956
var args = cmd._args.map(function(arg) {
957
return humanReadableArgName(arg);
958
}).join(' ');
959
960
return [
961
cmd._name
962
+ (cmd._alias ? '|' + cmd._alias : '')
963
+ (cmd.options.length ? ' [options]' : '')
964
+ ' ' + args
965
, cmd._description
966
];
967
});
968
969
var width = commands.reduce(function(max, command) {
970
return Math.max(max, command[0].length);
971
}, 0);
972
973
return [
974
''
975
, ' Commands:'
976
, ''
977
, commands.map(function(cmd) {
978
var desc = cmd[1] ? ' ' + cmd[1] : '';
979
return pad(cmd[0], width) + desc;
980
}).join('\n').replace(/^/gm, ' ')
981
, ''
982
].join('\n');
983
};
984
985
/**
986
* Return program help documentation.
987
*
988
* @return {String}
989
* @api private
990
*/
991
992
Command.prototype.helpInformation = function() {
993
var desc = [];
994
if (this._description) {
995
desc = [
996
' ' + this._description
997
, ''
998
];
999
}
1000
1001
var cmdName = this._name;
1002
if (this._alias) {
1003
cmdName = cmdName + '|' + this._alias;
1004
}
1005
var usage = [
1006
''
1007
,' Usage: ' + cmdName + ' ' + this.usage()
1008
, ''
1009
];
1010
1011
var cmds = [];
1012
var commandHelp = this.commandHelp();
1013
if (commandHelp) cmds = [commandHelp];
1014
1015
var options = [
1016
' Options:'
1017
, ''
1018
, '' + this.optionHelp().replace(/^/gm, ' ')
1019
, ''
1020
, ''
1021
];
1022
1023
return usage
1024
.concat(cmds)
1025
.concat(desc)
1026
.concat(options)
1027
.join('\n');
1028
};
1029
1030
/**
1031
* Output help information for this command
1032
*
1033
* @api public
1034
*/
1035
1036
Command.prototype.outputHelp = function(cb) {
1037
if (!cb) {
1038
cb = function(passthru) {
1039
return passthru;
1040
}
1041
}
1042
process.stdout.write(cb(this.helpInformation()));
1043
this.emit('--help');
1044
};
1045
1046
/**
1047
* Output help information and exit.
1048
*
1049
* @api public
1050
*/
1051
1052
Command.prototype.help = function(cb) {
1053
this.outputHelp(cb);
1054
process.exit();
1055
};
1056
1057
/**
1058
* Camel-case the given `flag`
1059
*
1060
* @param {String} flag
1061
* @return {String}
1062
* @api private
1063
*/
1064
1065
function camelcase(flag) {
1066
return flag.split('-').reduce(function(str, word) {
1067
return str + word[0].toUpperCase() + word.slice(1);
1068
});
1069
}
1070
1071
/**
1072
* Pad `str` to `width`.
1073
*
1074
* @param {String} str
1075
* @param {Number} width
1076
* @return {String}
1077
* @api private
1078
*/
1079
1080
function pad(str, width) {
1081
var len = Math.max(0, width - str.length);
1082
return str + Array(len + 1).join(' ');
1083
}
1084
1085
/**
1086
* Output help information if necessary
1087
*
1088
* @param {Command} command to output help for
1089
* @param {Array} array of options to search for -h or --help
1090
* @api private
1091
*/
1092
1093
function outputHelpIfNecessary(cmd, options) {
1094
options = options || [];
1095
for (var i = 0; i < options.length; i++) {
1096
if (options[i] == '--help' || options[i] == '-h') {
1097
cmd.outputHelp();
1098
process.exit(0);
1099
}
1100
}
1101
}
1102
1103
/**
1104
* Takes an argument an returns its human readable equivalent for help usage.
1105
*
1106
* @param {Object} arg
1107
* @return {String}
1108
* @api private
1109
*/
1110
1111
function humanReadableArgName(arg) {
1112
var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
1113
1114
return arg.required
1115
? '<' + nameOutput + '>'
1116
: '[' + nameOutput + ']'
1117
}
1118
1119
// for versions before node v0.8 when there weren't `fs.existsSync`
1120
function exists(file) {
1121
try {
1122
if (fs.statSync(file).isFile()) {
1123
return true;
1124
}
1125
} catch (e) {
1126
return false;
1127
}
1128
}
1129
1130
1131