index.js 22 KB

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