index.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638
  1. var path = require('path');
  2. var minimist = require('./lib/minimist');
  3. var wordwrap = require('./lib/wordwrap');
  4. /* Hack an instance of Argv with process.argv into Argv
  5. so people can do
  6. require('yargs')(['--beeble=1','-z','zizzle']).argv
  7. to parse a list of args and
  8. require('yargs').argv
  9. to get a parsed version of process.argv.
  10. */
  11. var inst = Argv(process.argv.slice(2));
  12. Object.keys(inst).forEach(function (key) {
  13. Argv[key] = typeof inst[key] == 'function'
  14. ? inst[key].bind(inst)
  15. : inst[key];
  16. });
  17. var exports = module.exports = Argv;
  18. function Argv (processArgs, cwd) {
  19. var self = {};
  20. if (!cwd) cwd = process.cwd();
  21. self.$0 = process.argv
  22. .slice(0,2)
  23. .map(function (x) {
  24. var b = rebase(cwd, x);
  25. return x.match(/^\//) && b.length < x.length
  26. ? b : x
  27. })
  28. .join(' ')
  29. ;
  30. if (process.env._ != undefined && process.argv[1] == process.env._) {
  31. self.$0 = process.env._.replace(
  32. path.dirname(process.execPath) + '/', ''
  33. );
  34. }
  35. var options;
  36. self.resetOptions = function () {
  37. options = {
  38. boolean: [],
  39. string: [],
  40. alias: {},
  41. default: [],
  42. requiresArg: [],
  43. count: [],
  44. normalize: [],
  45. config: []
  46. };
  47. return self;
  48. };
  49. self.resetOptions();
  50. self.boolean = function (bools) {
  51. options.boolean.push.apply(options.boolean, [].concat(bools));
  52. return self;
  53. };
  54. self.normalize = function (strings) {
  55. options.normalize.push.apply(options.normalize, [].concat(strings));
  56. return self;
  57. };
  58. self.config = function (configs) {
  59. options.config.push.apply(options.config, [].concat(configs));
  60. return self;
  61. };
  62. var examples = [];
  63. self.example = function (cmd, description) {
  64. examples.push([cmd, description]);
  65. return self;
  66. };
  67. self.string = function (strings) {
  68. options.string.push.apply(options.string, [].concat(strings));
  69. return self;
  70. };
  71. self.default = function (key, value) {
  72. if (typeof key === 'object') {
  73. Object.keys(key).forEach(function (k) {
  74. self.default(k, key[k]);
  75. });
  76. }
  77. else {
  78. options.default[key] = value;
  79. }
  80. return self;
  81. };
  82. self.alias = function (x, y) {
  83. if (typeof x === 'object') {
  84. Object.keys(x).forEach(function (key) {
  85. self.alias(key, x[key]);
  86. });
  87. }
  88. else {
  89. options.alias[x] = (options.alias[x] || []).concat(y);
  90. }
  91. return self;
  92. };
  93. self.count = function(counts) {
  94. options.count.push.apply(options.count, [].concat(counts));
  95. return self;
  96. };
  97. var demanded = {};
  98. self.demand = self.required = self.require = function (keys, msg) {
  99. if (typeof keys == 'number') {
  100. if (!demanded._) demanded._ = { count: 0, msg: null };
  101. demanded._.count += keys;
  102. demanded._.msg = msg;
  103. }
  104. else if (Array.isArray(keys)) {
  105. keys.forEach(function (key) {
  106. self.demand(key, msg);
  107. });
  108. }
  109. else {
  110. if (typeof msg === 'string') {
  111. demanded[keys] = { msg: msg };
  112. }
  113. else if (msg === true || typeof msg === 'undefined') {
  114. demanded[keys] = { msg: null };
  115. }
  116. }
  117. return self;
  118. };
  119. self.requiresArg = function (requiresArgs) {
  120. options.requiresArg.push.apply(options.requiresArg, [].concat(requiresArgs));
  121. return self;
  122. };
  123. var implied = {};
  124. self.implies = function (key, value) {
  125. if (typeof key === 'object') {
  126. Object.keys(key).forEach(function (k) {
  127. self.implies(k, key[k]);
  128. });
  129. } else {
  130. implied[key] = value;
  131. }
  132. return self;
  133. };
  134. var usage;
  135. self.usage = function (msg, opts) {
  136. if (!opts && typeof msg === 'object') {
  137. opts = msg;
  138. msg = null;
  139. }
  140. usage = msg;
  141. if (opts) self.options(opts);
  142. return self;
  143. };
  144. var fails = [];
  145. self.fail = function (f) {
  146. fails.push(f);
  147. return self;
  148. };
  149. function fail (msg) {
  150. if (fails.length) {
  151. fails.forEach(function (f) {
  152. f(msg);
  153. });
  154. } else {
  155. if (showHelpOnFail) {
  156. self.showHelp();
  157. }
  158. if (msg) console.error(msg);
  159. if (failMessage) {
  160. if (msg) {
  161. console.error("");
  162. }
  163. console.error(failMessage);
  164. }
  165. process.exit(1);
  166. }
  167. }
  168. var checks = [];
  169. self.check = function (f) {
  170. checks.push(f);
  171. return self;
  172. };
  173. self.defaults = self.default;
  174. var descriptions = {};
  175. self.describe = function (key, desc) {
  176. if (typeof key === 'object') {
  177. Object.keys(key).forEach(function (k) {
  178. self.describe(k, key[k]);
  179. });
  180. }
  181. else {
  182. descriptions[key] = desc;
  183. }
  184. return self;
  185. };
  186. self.parse = function (args) {
  187. return parseArgs(args);
  188. };
  189. self.option = self.options = function (key, opt) {
  190. if (typeof key === 'object') {
  191. Object.keys(key).forEach(function (k) {
  192. self.options(k, key[k]);
  193. });
  194. }
  195. else {
  196. if (opt.alias) self.alias(key, opt.alias);
  197. var demand = opt.demand || opt.required || opt.require;
  198. if (demand) {
  199. self.demand(key, demand);
  200. }
  201. if (typeof opt.default !== 'undefined') {
  202. self.default(key, opt.default);
  203. }
  204. if (opt.boolean || opt.type === 'boolean') {
  205. self.boolean(key);
  206. if (opt.alias) self.boolean(opt.alias);
  207. }
  208. if (opt.string || opt.type === 'string') {
  209. self.string(key);
  210. if (opt.alias) self.string(opt.alias);
  211. }
  212. if (opt.count || opt.type === 'count') {
  213. self.count(key);
  214. }
  215. var desc = opt.describe || opt.description || opt.desc;
  216. if (desc) {
  217. self.describe(key, desc);
  218. }
  219. if (opt.requiresArg) {
  220. self.requiresArg(key);
  221. }
  222. }
  223. return self;
  224. };
  225. var wrap = null;
  226. self.wrap = function (cols) {
  227. wrap = cols;
  228. return self;
  229. };
  230. var strict = false;
  231. self.strict = function () {
  232. strict = true;
  233. return self;
  234. };
  235. self.showHelp = function (fn) {
  236. if (!fn) fn = console.error.bind(console);
  237. fn(self.help());
  238. return self;
  239. };
  240. var version = null;
  241. var versionOpt = null;
  242. self.version = function (ver, opt, msg) {
  243. version = ver;
  244. versionOpt = opt;
  245. self.describe(opt, msg || 'Show version number');
  246. return self;
  247. };
  248. var helpOpt = null;
  249. self.addHelpOpt = function (opt, msg) {
  250. helpOpt = opt;
  251. self.describe(opt, msg || 'Show help');
  252. return self;
  253. };
  254. var failMessage = null;
  255. var showHelpOnFail = true;
  256. self.showHelpOnFail = function (enabled, message) {
  257. if (typeof enabled === 'string') {
  258. enabled = true;
  259. message = enabled;
  260. }
  261. else if (typeof enabled === 'undefined') {
  262. enabled = true;
  263. }
  264. failMessage = message;
  265. showHelpOnFail = enabled;
  266. return self;
  267. };
  268. self.help = function () {
  269. if (arguments.length > 0) {
  270. return self.addHelpOpt.apply(self, arguments);
  271. }
  272. var keys = Object.keys(
  273. Object.keys(descriptions)
  274. .concat(Object.keys(demanded))
  275. .concat(Object.keys(options.default))
  276. .reduce(function (acc, key) {
  277. if (key !== '_') acc[key] = true;
  278. return acc;
  279. }, {})
  280. );
  281. var help = keys.length ? [ 'Options:' ] : [];
  282. if (examples.length) {
  283. help.unshift('');
  284. examples.forEach(function (example) {
  285. example[0] = example[0].replace(/\$0/g, self.$0);
  286. });
  287. var commandlen = longest(examples.map(function (a) {
  288. return a[0];
  289. }));
  290. var exampleLines = examples.map(function(example) {
  291. var command = example[0];
  292. var description = example[1];
  293. command += Array(commandlen + 5 - command.length).join(' ');
  294. return ' ' + command + description;
  295. });
  296. exampleLines.push('');
  297. help = exampleLines.concat(help);
  298. help.unshift('Examples:');
  299. }
  300. if (usage) {
  301. help.unshift(usage.replace(/\$0/g, self.$0), '');
  302. }
  303. var aliasKeys = (Object.keys(options.alias) || [])
  304. .concat(Object.keys(self.parsed.newAliases) || []);
  305. keys = keys.filter(function(key) {
  306. return !self.parsed.newAliases[key] && aliasKeys.every(function(alias) {
  307. return -1 == (options.alias[alias] || []).indexOf(key);
  308. });
  309. });
  310. var switches = keys.reduce(function (acc, key) {
  311. acc[key] = [ key ].concat(options.alias[key] || [])
  312. .map(function (sw) {
  313. return (sw.length > 1 ? '--' : '-') + sw
  314. })
  315. .join(', ')
  316. ;
  317. return acc;
  318. }, {});
  319. var switchlen = longest(Object.keys(switches).map(function (s) {
  320. return switches[s] || '';
  321. }));
  322. var desclen = longest(Object.keys(descriptions).map(function (d) {
  323. return descriptions[d] || '';
  324. }));
  325. keys.forEach(function (key) {
  326. var kswitch = switches[key];
  327. var desc = descriptions[key] || '';
  328. if (wrap) {
  329. desc = wordwrap(switchlen + 4, wrap)(desc)
  330. .slice(switchlen + 4)
  331. ;
  332. }
  333. var spadding = new Array(
  334. Math.max(switchlen - kswitch.length + 3, 0)
  335. ).join(' ');
  336. var dpadding = new Array(
  337. Math.max(desclen - desc.length + 1, 0)
  338. ).join(' ');
  339. var type = null;
  340. if (options.boolean[key]) type = '[boolean]';
  341. if (options.count[key]) type = '[count]';
  342. if (options.string[key]) type = '[string]';
  343. if (options.normalize[key]) type = '[string]';
  344. if (!wrap && dpadding.length > 0) {
  345. desc += dpadding;
  346. }
  347. var prelude = ' ' + kswitch + spadding;
  348. var extra = [
  349. type,
  350. demanded[key]
  351. ? '[required]'
  352. : null
  353. ,
  354. options.default[key] !== undefined
  355. ? '[default: ' + (typeof options.default[key] === 'string' ?
  356. JSON.stringify : String)(options.default[key]) + ']'
  357. : null
  358. ].filter(Boolean).join(' ');
  359. var body = [ desc, extra ].filter(Boolean).join(' ');
  360. if (wrap) {
  361. var dlines = desc.split('\n');
  362. var dlen = dlines.slice(-1)[0].length
  363. + (dlines.length === 1 ? prelude.length : 0)
  364. body = desc + (dlen + extra.length > wrap - 2
  365. ? '\n'
  366. + new Array(wrap - extra.length + 1).join(' ')
  367. + extra
  368. : new Array(wrap - extra.length - dlen + 1).join(' ')
  369. + extra
  370. );
  371. }
  372. help.push(prelude + body);
  373. });
  374. if (keys.length) help.push('');
  375. return help.join('\n');
  376. };
  377. Object.defineProperty(self, 'argv', {
  378. get : function () { return parseArgs(processArgs) },
  379. enumerable : true
  380. });
  381. function parseArgs (args) {
  382. var parsed = minimist(args, options),
  383. argv = parsed.argv,
  384. aliases = parsed.aliases;
  385. argv.$0 = self.$0;
  386. self.parsed = parsed;
  387. Object.keys(argv).forEach(function(key) {
  388. if (key === helpOpt) {
  389. self.showHelp(console.log);
  390. process.exit(0);
  391. }
  392. else if (key === versionOpt) {
  393. process.stdout.write(version);
  394. process.exit(0);
  395. }
  396. });
  397. if (demanded._ && argv._.length < demanded._.count) {
  398. if (demanded._.msg) {
  399. fail(demanded._.msg);
  400. } else {
  401. fail('Not enough non-option arguments: got '
  402. + argv._.length + ', need at least ' + demanded._.count
  403. );
  404. }
  405. }
  406. if (options.requiresArg.length > 0) {
  407. var missingRequiredArgs = [];
  408. options.requiresArg.forEach(function(key) {
  409. var value = argv[key];
  410. // minimist sets --foo value to true / --no-foo to false
  411. if (value === true || value === false) {
  412. missingRequiredArgs.push(key);
  413. }
  414. });
  415. if (missingRequiredArgs.length == 1) {
  416. fail("Missing argument value: " + missingRequiredArgs[0]);
  417. }
  418. else if (missingRequiredArgs.length > 1) {
  419. message = "Missing argument values: " + missingRequiredArgs.join(", ");
  420. fail(message);
  421. }
  422. }
  423. var missing = null;
  424. Object.keys(demanded).forEach(function (key) {
  425. if (!argv.hasOwnProperty(key)) {
  426. missing = missing || {};
  427. missing[key] = demanded[key];
  428. }
  429. });
  430. if (missing) {
  431. var customMsgs = [];
  432. Object.keys(missing).forEach(function(key) {
  433. var msg = missing[key].msg;
  434. if (msg && customMsgs.indexOf(msg) < 0) {
  435. customMsgs.push(msg);
  436. }
  437. });
  438. var customMsg = customMsgs.length ? '\n' + customMsgs.join('\n') : '';
  439. fail('Missing required arguments: ' + Object.keys(missing).join(', ') + customMsg);
  440. }
  441. if (strict) {
  442. var unknown = [];
  443. var aliases = {};
  444. Object.keys(parsed.aliases).forEach(function (key) {
  445. parsed.aliases[key].forEach(function (alias) {
  446. aliases[alias] = key;
  447. });
  448. });
  449. Object.keys(argv).forEach(function (key) {
  450. if (key !== "$0" && key !== "_" &&
  451. !descriptions.hasOwnProperty(key) &&
  452. !demanded.hasOwnProperty(key) &&
  453. !aliases.hasOwnProperty(key)) {
  454. unknown.push(key);
  455. }
  456. });
  457. if (unknown.length == 1) {
  458. fail("Unknown argument: " + unknown[0]);
  459. }
  460. else if (unknown.length > 1) {
  461. fail("Unknown arguments: " + unknown.join(", "));
  462. }
  463. }
  464. checks.forEach(function (f) {
  465. try {
  466. var result = f(argv, aliases);
  467. if (result === false) {
  468. fail('Argument check failed: ' + f.toString());
  469. } else if (typeof result === 'string') {
  470. fail(result);
  471. }
  472. }
  473. catch (err) {
  474. fail(err)
  475. }
  476. });
  477. var implyFail = [];
  478. Object.keys(implied).forEach(function (key) {
  479. var num, origKey = key, value = implied[key];
  480. // convert string '1' to number 1
  481. var num = Number(key);
  482. key = isNaN(num) ? key : num;
  483. if (typeof key === 'number') {
  484. // check length of argv._
  485. key = argv._.length >= key;
  486. } else if (key.match(/^--no-.+/)) {
  487. // check if key doesn't exist
  488. key = key.match(/^--no-(.+)/)[1];
  489. key = !argv[key];
  490. } else {
  491. // check if key exists
  492. key = argv[key];
  493. }
  494. num = Number(value);
  495. value = isNaN(num) ? value : num;
  496. if (typeof value === 'number') {
  497. value = argv._.length >= value;
  498. } else if (value.match(/^--no-.+/)) {
  499. value = value.match(/^--no-(.+)/)[1];
  500. value = !argv[value];
  501. } else {
  502. value = argv[value];
  503. }
  504. if (key && !value) {
  505. implyFail.push(origKey);
  506. }
  507. });
  508. if (implyFail.length) {
  509. var msg = 'Implications failed:\n';
  510. implyFail.forEach(function (key) {
  511. msg += (' ' + key + ' -> ' + implied[key] + '\n');
  512. });
  513. fail(msg);
  514. }
  515. return argv;
  516. }
  517. function longest (xs) {
  518. return Math.max.apply(
  519. null,
  520. xs.map(function (x) { return x.length })
  521. );
  522. }
  523. return self;
  524. };
  525. // rebase an absolute path to a relative one with respect to a base directory
  526. // exported for tests
  527. exports.rebase = rebase;
  528. function rebase (base, dir) {
  529. var ds = path.normalize(dir).split('/').slice(1);
  530. var bs = path.normalize(base).split('/').slice(1);
  531. for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
  532. ds.splice(0, i); bs.splice(0, i);
  533. var p = path.normalize(
  534. bs.map(function () { return '..' }).concat(ds).join('/')
  535. ).replace(/\/$/,'').replace(/^$/, '.');
  536. return p.match(/^[.\/]/) ? p : './' + p;
  537. };