Нет описания

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. var fs = require('fs');
  2. var path = require('path');
  3. var nopt = require('nopt');
  4. var util = require('./util');
  5. var events = require('events');
  6. var collectable = require('./actions/collectable');
  7. var commandable = require('./actions/commandable');
  8. module.exports = noptify;
  9. noptify.Noptify = Noptify;
  10. // noptify is a little wrapper around `nopt` module adding a more expressive,
  11. // commander-like, API and few helpers.
  12. //
  13. // Examples
  14. //
  15. // var program = noptify(process.argv, { program: 'name' })
  16. // .version('0.0.1')
  17. // .option('port', '-p', 'Port to listen on (default: 35729)', Number)
  18. // .option('pid', 'Path to the generated PID file', String)
  19. //
  20. // var opts = program.parse();
  21. //
  22. // Returns an instance of `Noptify`
  23. function noptify(args, options) {
  24. return new Noptify(args, options);
  25. }
  26. // Noptify provides the API to parse out option, shorthands and generate the
  27. // proper generic help output.
  28. //
  29. // - args - The Array of arguments to parse (default: `process.argv`);
  30. // - options - An hash of options with the following properties
  31. // - program - The program name to use in usage output
  32. //
  33. // Every noptify instance is created with two options, `-h, --help` and `-v,
  34. // --version`.
  35. function Noptify(args, options) {
  36. events.EventEmitter.call(this);
  37. options = this.options = options || {};
  38. this.args = args || process.argv;
  39. this._program = options.program || (path.basename(this.args[this.args[0] === 'node' ? 1 : 0]));
  40. this._shorthands = {};
  41. this._commands = {};
  42. this._routes = [];
  43. this._steps = [];
  44. this.nopt = {};
  45. this.option('help', '-h', 'Show help usage');
  46. this.option('version', '-v', 'Show package version');
  47. }
  48. util.inherits(Noptify, events.EventEmitter);
  49. // Inherits from each actions' mixins
  50. //
  51. // XXX: consider making it optional? with a `.use()` method?
  52. util.extend(Noptify.prototype, collectable);
  53. util.extend(Noptify.prototype, commandable);
  54. // Parse the provided options and shorthands, pass them through `nopt` and
  55. // return the result.
  56. //
  57. // When `opts.help` is set, the help output is displayed and `help`
  58. // event is emitted. The process exists with `0` status, the help output is
  59. // automatically displayed and the `help` event is emitted.
  60. //
  61. // Examples
  62. //
  63. // var program = noptify(['foo', '--help'])
  64. // .on('help', function() {
  65. // console.log('Examples');
  66. // console.log('');
  67. // console.log(' foo bar --baz > foo.txt');
  68. // });
  69. //
  70. // var opts = program.parse();
  71. // // ... Help output ...
  72. // // ... Custom help output ...
  73. // // ... Exit ...
  74. //
  75. //
  76. Noptify.prototype.parse = function parse(argv, position) {
  77. argv = argv || this.args;
  78. var options = this._options.reduce(function(opts, opt) {
  79. opts[opt.name] = opt.type;
  80. return opts;
  81. }, {});
  82. this._options.forEach(function(opt) {
  83. if(!opt.shorthand) return;
  84. this.shorthand(opt.shorthand, '--' + opt.name);
  85. }, this);
  86. var opts = nopt(options, this._shorthands, argv, position);
  87. if(opts.version) {
  88. console.log(this._version);
  89. process.exit(0);
  90. }
  91. var registered = this.registered(opts.argv.remain);
  92. if(opts.help) {
  93. if(registered && registered.help) {
  94. registered.help().emit('help');
  95. } else {
  96. this.help().emit('help');
  97. }
  98. process.exit(0);
  99. }
  100. this.nopt = opts;
  101. // check remaining args and registered command, for each match, remove the
  102. // argument from remaining args and trigger the handler, ideally the handler
  103. // can be another subprogram, for now simple functions
  104. if(this.routeCommand) process.nextTick(this.routeCommand.bind(this));
  105. if(this.stdin && this._readFromStdin) process.nextTick(this.stdin.bind(this));
  106. return opts;
  107. };
  108. // Define the program version.
  109. Noptify.prototype.version = function version(ver) {
  110. this._version = ver;
  111. return this;
  112. };
  113. // Define the program property.
  114. Noptify.prototype.program = function program(value) {
  115. this._program = value || '';
  116. return this;
  117. };
  118. // Define `name` option with optional shorthands, optional description and optional type.
  119. Noptify.prototype.option = function option(name, shorthand, description, type) {
  120. this._options = this._options || [];
  121. if(!description) {
  122. description = shorthand;
  123. shorthand = '';
  124. }
  125. if(!type) {
  126. if(typeof description === 'function') {
  127. type = description;
  128. description = shorthand;
  129. shorthand = '';
  130. } else {
  131. type = String;
  132. }
  133. }
  134. shorthand = shorthand.replace(/^-*/, ''),
  135. this._options.push({
  136. name: name,
  137. shorthand: shorthand.replace(/^-*/, ''),
  138. description: description || (name + ': ' + type.name),
  139. usage: (shorthand ? '-' + shorthand + ', ': '' ) + '--' + name,
  140. type: type
  141. });
  142. return this;
  143. };
  144. // Stores the given `shorthands` Hash of options to be `parse()`-d by nopt
  145. // later on.
  146. Noptify.prototype.shorthand =
  147. Noptify.prototype.shorthands = function shorthands(options, value) {
  148. if(typeof options === 'string' && value) {
  149. this._shorthands[options] = value;
  150. return this;
  151. }
  152. Object.keys(options).forEach(function(shorthand) {
  153. this._shorthands[shorthand] = options[shorthand];
  154. }, this);
  155. return this;
  156. };
  157. // Simply output to stdout the Usage and Help output.
  158. Noptify.prototype.help = function help() {
  159. var buf = '';
  160. buf += '\n Usage: ' + this._program + ' [options]';
  161. buf += '\n';
  162. buf += '\n Options:\n';
  163. var maxln = Math.max.apply(Math, this._options.map(function(opts) {
  164. return opts.usage.length;
  165. }));
  166. var options = this._options.map(function(opts) {
  167. return ' ' + pad(opts.usage, maxln + 5) + '\t- ' + opts.description;
  168. });
  169. buf += options.join('\n');
  170. // part of help input ? --list-shorthands ?
  171. var shorthands = Object.keys(this._shorthands);
  172. if(shorthands.length) {
  173. buf += '\n\n Shorthands:\n';
  174. maxln = Math.max.apply(Math, Object.keys(this._shorthands).map(function(key) {
  175. return key.length;
  176. }));
  177. buf += Object.keys(this._shorthands).map(function(shorthand) {
  178. return ' --' + pad(shorthand, maxln + 1) + '\t\t' + this._shorthands[shorthand];
  179. }, this).join('\n');
  180. }
  181. buf += '\n';
  182. console.log(buf);
  183. return this;
  184. };
  185. // Helpers
  186. // command API
  187. Noptify.prototype.run = function run(fn) {
  188. if(fn) {
  189. this._steps.push(fn);
  190. return this;
  191. }
  192. if(!this.nopt.argv) return this.parse();
  193. var steps = this._steps;
  194. var self = this;
  195. (function next(step) {
  196. if(!step) return;
  197. var async = /function\s*\(\w+/.test(step + '');
  198. if(!async) {
  199. step();
  200. return next(steps.shift());
  201. }
  202. step(function(err) {
  203. if(err) return self.emit('error', err);
  204. next(steps.shift());
  205. });
  206. })(steps.shift());
  207. };
  208. function pad(str, max) {
  209. var ln = max - str.length;
  210. return ln > 0 ? str + new Array(ln).join(' ') : str;
  211. }