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