commander.js 23 KB

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