index.js 24 KB

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