You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

index.js 24KB

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