index.js 24 KB

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