index.js 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489
  1. var assert = require('assert'),
  2. path = require('path'),
  3. Completion = require('./lib/completion'),
  4. Parser = require('./lib/parser'),
  5. Usage = require('./lib/usage'),
  6. Validation = require('./lib/validation');
  7. Argv(process.argv.slice(2));
  8. var exports = module.exports = Argv;
  9. function Argv (processArgs, cwd) {
  10. processArgs = processArgs || []; // handle calling yargs().
  11. var self = {};
  12. var completion = null;
  13. var usage = null;
  14. var validation = null;
  15. if (!cwd) cwd = process.cwd();
  16. self.$0 = process.argv
  17. .slice(0,2)
  18. .map(function (x) {
  19. // ignore the node bin, specify this in your
  20. // bin file with #!/usr/bin/env node
  21. if (~x.indexOf('node')) return;
  22. var b = rebase(cwd, x);
  23. return x.match(/^\//) && b.length < x.length
  24. ? b : x
  25. })
  26. .join(' ').trim();
  27. ;
  28. if (process.env._ != undefined && process.argv[1] == process.env._) {
  29. self.$0 = process.env._.replace(
  30. path.dirname(process.execPath) + '/', ''
  31. );
  32. }
  33. var options;
  34. self.resetOptions = self.reset = function () {
  35. // put yargs back into its initial
  36. // state, this is useful for creating a
  37. // nested CLI.
  38. options = {
  39. array: [],
  40. boolean: [],
  41. string: [],
  42. narg: {},
  43. key: {},
  44. alias: {},
  45. default: {},
  46. defaultDescription: {},
  47. requiresArg: [],
  48. count: [],
  49. normalize: [],
  50. config: []
  51. };
  52. usage = Usage(self); // handle usage output.
  53. validation = Validation(self, usage); // handle arg validation.
  54. completion = Completion(self, usage);
  55. demanded = {};
  56. exitProcess = true;
  57. strict = false;
  58. helpOpt = null;
  59. versionOpt = null;
  60. completionOpt = null;
  61. return self;
  62. };
  63. self.resetOptions();
  64. self.boolean = function (bools) {
  65. options.boolean.push.apply(options.boolean, [].concat(bools));
  66. return self;
  67. };
  68. self.array = function (arrays) {
  69. options.array.push.apply(options.array, [].concat(arrays));
  70. return self;
  71. }
  72. self.nargs = function (key, n) {
  73. if (typeof key === 'object') {
  74. Object.keys(key).forEach(function(k) {
  75. self.nargs(k, key[k]);
  76. });
  77. } else {
  78. options.narg[key] = n;
  79. }
  80. return self;
  81. }
  82. self.normalize = function (strings) {
  83. options.normalize.push.apply(options.normalize, [].concat(strings));
  84. return self;
  85. };
  86. self.config = function (configs) {
  87. options.config.push.apply(options.config, [].concat(configs));
  88. return self;
  89. };
  90. self.example = function (cmd, description) {
  91. usage.example(cmd, description);
  92. return self;
  93. };
  94. self.command = function (cmd, description) {
  95. usage.command(cmd, description);
  96. return self;
  97. };
  98. self.string = function (strings) {
  99. options.string.push.apply(options.string, [].concat(strings));
  100. return self;
  101. };
  102. self.default = function (key, value, defaultDescription) {
  103. if (typeof key === 'object') {
  104. Object.keys(key).forEach(function (k) {
  105. self.default(k, key[k]);
  106. });
  107. }
  108. else {
  109. options.defaultDescription[key] = defaultDescription;
  110. options.default[key] = value;
  111. }
  112. return self;
  113. };
  114. self.alias = function (x, y) {
  115. if (typeof x === 'object') {
  116. Object.keys(x).forEach(function (key) {
  117. self.alias(key, x[key]);
  118. });
  119. }
  120. else {
  121. options.alias[x] = (options.alias[x] || []).concat(y);
  122. }
  123. return self;
  124. };
  125. self.count = function(counts) {
  126. options.count.push.apply(options.count, [].concat(counts));
  127. return self;
  128. };
  129. var demanded = {};
  130. self.demand = self.required = self.require = function (keys, msg) {
  131. if (typeof keys == 'number') {
  132. if (!demanded._) demanded._ = { count: 0, msg: null };
  133. demanded._.count += keys;
  134. demanded._.msg = msg;
  135. }
  136. else if (Array.isArray(keys)) {
  137. keys.forEach(function (key) {
  138. self.demand(key, msg);
  139. });
  140. }
  141. else {
  142. if (typeof msg === 'string') {
  143. demanded[keys] = { msg: msg };
  144. }
  145. else if (msg === true || typeof msg === 'undefined') {
  146. demanded[keys] = { msg: null };
  147. }
  148. }
  149. return self;
  150. };
  151. self.getDemanded = function() {
  152. return demanded;
  153. };
  154. self.requiresArg = function (requiresArgs) {
  155. options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs));
  156. return self;
  157. };
  158. self.implies = function (key, value) {
  159. validation.implies(key, value);
  160. return self;
  161. };
  162. self.usage = function (msg, opts) {
  163. if (!opts && typeof msg === 'object') {
  164. opts = msg;
  165. msg = null;
  166. }
  167. usage.usage(msg);
  168. if (opts) self.options(opts);
  169. return self;
  170. };
  171. self.epilogue = self.epilog = function (msg) {
  172. usage.epilog(msg);
  173. return self;
  174. };
  175. self.fail = function (f) {
  176. usage.failFn(f);
  177. return self;
  178. };
  179. self.check = function (f) {
  180. validation.check(f);
  181. return self;
  182. };
  183. self.defaults = self.default;
  184. self.describe = function (key, desc) {
  185. usage.describe(key, desc);
  186. return self;
  187. };
  188. self.parse = function (args) {
  189. return parseArgs(args);
  190. };
  191. self.option = self.options = function (key, opt) {
  192. if (typeof key === 'object') {
  193. Object.keys(key).forEach(function (k) {
  194. self.options(k, key[k]);
  195. });
  196. }
  197. else {
  198. assert(typeof opt === 'object', 'second argument to option must be an object');
  199. options.key[key] = true; // track manually set keys.
  200. if (opt.alias) self.alias(key, opt.alias);
  201. var demand = opt.demand || opt.required || opt.require;
  202. if (demand) {
  203. self.demand(key, demand);
  204. }
  205. if ('default' in opt) {
  206. self.default(key, opt.default);
  207. }
  208. if ('nargs' in opt) {
  209. self.nargs(key, opt.nargs);
  210. }
  211. if (opt.boolean || opt.type === 'boolean') {
  212. self.boolean(key);
  213. if (opt.alias) self.boolean(opt.alias);
  214. }
  215. if (opt.array || opt.type === 'array') {
  216. self.array(key);
  217. if (opt.alias) self.array(opt.alias);
  218. }
  219. if (opt.string || opt.type === 'string') {
  220. self.string(key);
  221. if (opt.alias) self.string(opt.alias);
  222. }
  223. if (opt.count || opt.type === 'count') {
  224. self.count(key);
  225. }
  226. var desc = opt.describe || opt.description || opt.desc;
  227. if (desc) {
  228. self.describe(key, desc);
  229. }
  230. if (opt.requiresArg) {
  231. self.requiresArg(key);
  232. }
  233. }
  234. return self;
  235. };
  236. self.getOptions = function() {
  237. return options;
  238. };
  239. self.wrap = function (cols) {
  240. usage.wrap(cols);
  241. return self;
  242. };
  243. var strict = false;
  244. self.strict = function () {
  245. strict = true;
  246. return self;
  247. };
  248. self.getStrict = function () {
  249. return strict;
  250. }
  251. self.showHelp = function (fn) {
  252. usage.showHelp(fn);
  253. return self;
  254. };
  255. var versionOpt = null;
  256. self.version = function (ver, opt, msg) {
  257. versionOpt = opt || 'version';
  258. usage.version(ver);
  259. self.describe(versionOpt, msg || 'Show version number');
  260. return self;
  261. };
  262. var helpOpt = null;
  263. self.addHelpOpt = function (opt, msg) {
  264. helpOpt = opt;
  265. self.describe(opt, msg || 'Show help');
  266. return self;
  267. };
  268. self.showHelpOnFail = function (enabled, message) {
  269. usage.showHelpOnFail(enabled, message);
  270. return self;
  271. };
  272. var exitProcess = true;
  273. self.exitProcess = function (enabled) {
  274. if (typeof enabled !== 'boolean') {
  275. enabled = true;
  276. }
  277. exitProcess = enabled;
  278. return self;
  279. };
  280. self.getExitProcess = function () {
  281. return exitProcess;
  282. }
  283. self.help = function () {
  284. if (arguments.length > 0) return self.addHelpOpt.apply(self, arguments);
  285. if (!self.parsed) parseArgs(processArgs); // run parser, if it has not already been executed.
  286. return usage.help();
  287. };
  288. var completionOpt = null,
  289. completionCommand = null;
  290. self.completion = function(cmd, desc, fn) {
  291. // a function to execute when generating
  292. // completions can be provided as the second
  293. // or third argument to completion.
  294. if (typeof desc === 'function') {
  295. fn = desc;
  296. desc = null;
  297. }
  298. // register the completion command.
  299. completionCommand = cmd;
  300. completionOpt = completion.completionKey;
  301. self.command(completionCommand, desc || 'generate bash completion script');
  302. // a function can be provided
  303. if (fn) completion.registerFunction(fn);
  304. return self;
  305. };
  306. self.showCompletionScript = function($0) {
  307. $0 = $0 || self.$0;
  308. console.log(completion.generateCompletionScript($0));
  309. return self;
  310. };
  311. self.getUsageInstance = function () {
  312. return usage;
  313. };
  314. self.getValidationInstance = function () {
  315. return validation;
  316. }
  317. Object.defineProperty(self, 'argv', {
  318. get : function () {
  319. var args = null;
  320. try {
  321. args = parseArgs(processArgs);
  322. } catch (err) {
  323. usage.fail(err.message);
  324. }
  325. return args;
  326. },
  327. enumerable : true
  328. });
  329. function parseArgs (args) {
  330. var parsed = Parser(args, options),
  331. argv = parsed.argv,
  332. aliases = parsed.aliases;
  333. argv.$0 = self.$0;
  334. self.parsed = parsed;
  335. // generate a completion script for adding to ~/.bashrc.
  336. if (completionCommand && ~argv._.indexOf(completionCommand)) {
  337. self.showCompletionScript();
  338. if (exitProcess){
  339. process.exit(0);
  340. }
  341. }
  342. Object.keys(argv).forEach(function(key) {
  343. if (key === helpOpt) {
  344. self.showHelp('log');
  345. if (exitProcess){
  346. process.exit(0);
  347. }
  348. }
  349. else if (key === versionOpt) {
  350. usage.showVersion();
  351. if (exitProcess){
  352. process.exit(0);
  353. }
  354. }
  355. else if (key === completionOpt) {
  356. // we allow for asynchronous completions,
  357. // e.g., loading in a list of commands from an API.
  358. completion.getCompletion(function(completions) {
  359. (completions || []).forEach(function(completion) {
  360. console.log(completion);
  361. });
  362. if (exitProcess){
  363. process.exit(0);
  364. }
  365. });
  366. return;
  367. }
  368. });
  369. validation.nonOptionCount(argv);
  370. validation.missingArgumentValue(argv);
  371. validation.requiredArguments(argv);
  372. if (strict) {
  373. validation.unknownArguments(argv, aliases);
  374. }
  375. validation.customChecks(argv, aliases);
  376. validation.implications(argv);
  377. setPlaceholderKeys(argv);
  378. return argv;
  379. }
  380. function setPlaceholderKeys (argv) {
  381. Object.keys(options.key).forEach(function(key) {
  382. if (typeof argv[key] === 'undefined') argv[key] = undefined;
  383. });
  384. }
  385. sigletonify(self);
  386. return self;
  387. };
  388. // rebase an absolute path to a relative one with respect to a base directory
  389. // exported for tests
  390. exports.rebase = rebase;
  391. function rebase (base, dir) {
  392. return path.relative(base, dir);
  393. };
  394. /* Hack an instance of Argv with process.argv into Argv
  395. so people can do
  396. require('yargs')(['--beeble=1','-z','zizzle']).argv
  397. to parse a list of args and
  398. require('yargs').argv
  399. to get a parsed version of process.argv.
  400. */
  401. function sigletonify(inst) {
  402. Object.keys(inst).forEach(function (key) {
  403. if (key === 'argv') {
  404. Argv.__defineGetter__(key, inst.__lookupGetter__(key));
  405. } else {
  406. Argv[key] = typeof inst[key] == 'function'
  407. ? inst[key].bind(inst)
  408. : inst[key];
  409. }
  410. });
  411. }