index.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. var path = require('path');
  2. var wordwrap = require('wordwrap');
  3. /* Hack an instance of Argv with process.argv into Argv
  4. so people can do
  5. require('optimist')(['--beeble=1','-z','zizzle']).argv
  6. to parse a list of args and
  7. require('optimist').argv
  8. to get a parsed version of process.argv.
  9. */
  10. var inst = Argv(process.argv.slice(2));
  11. Object.keys(inst).forEach(function (key) {
  12. Argv[key] = typeof inst[key] == 'function'
  13. ? inst[key].bind(inst)
  14. : inst[key];
  15. });
  16. var exports = module.exports = Argv;
  17. function Argv (args, cwd) {
  18. var self = {};
  19. if (!cwd) cwd = process.cwd();
  20. self.$0 = process.argv
  21. .slice(0,2)
  22. .map(function (x) {
  23. var b = rebase(cwd, x);
  24. return x.match(/^\//) && b.length < x.length
  25. ? b : x
  26. })
  27. .join(' ')
  28. ;
  29. if (process.env._ != undefined && process.argv[1] == process.env._) {
  30. self.$0 = process.env._.replace(
  31. path.dirname(process.execPath) + '/', ''
  32. );
  33. }
  34. var flags = { bools : {}, strings : {} };
  35. self.boolean = function (bools) {
  36. if (!Array.isArray(bools)) {
  37. bools = [].slice.call(arguments);
  38. }
  39. bools.forEach(function (name) {
  40. flags.bools[name] = true;
  41. });
  42. return self;
  43. };
  44. self.string = function (strings) {
  45. if (!Array.isArray(strings)) {
  46. strings = [].slice.call(arguments);
  47. }
  48. strings.forEach(function (name) {
  49. flags.strings[name] = true;
  50. });
  51. return self;
  52. };
  53. var aliases = {};
  54. self.alias = function (x, y) {
  55. if (typeof x === 'object') {
  56. Object.keys(x).forEach(function (key) {
  57. self.alias(key, x[key]);
  58. });
  59. }
  60. else if (Array.isArray(y)) {
  61. y.forEach(function (yy) {
  62. self.alias(x, yy);
  63. });
  64. }
  65. else {
  66. var zs = (aliases[x] || []).concat(aliases[y] || []).concat(x, y);
  67. aliases[x] = zs.filter(function (z) { return z != x });
  68. aliases[y] = zs.filter(function (z) { return z != y });
  69. }
  70. return self;
  71. };
  72. var demanded = {};
  73. self.demand = function (keys) {
  74. if (typeof keys == 'number') {
  75. if (!demanded._) demanded._ = 0;
  76. demanded._ += keys;
  77. }
  78. else if (Array.isArray(keys)) {
  79. keys.forEach(function (key) {
  80. self.demand(key);
  81. });
  82. }
  83. else {
  84. demanded[keys] = true;
  85. }
  86. return self;
  87. };
  88. var usage;
  89. self.usage = function (msg, opts) {
  90. if (!opts && typeof msg === 'object') {
  91. opts = msg;
  92. msg = null;
  93. }
  94. usage = msg;
  95. if (opts) self.options(opts);
  96. return self;
  97. };
  98. function fail (msg) {
  99. self.showHelp();
  100. if (msg) console.error(msg);
  101. process.exit(1);
  102. }
  103. var checks = [];
  104. self.check = function (f) {
  105. checks.push(f);
  106. return self;
  107. };
  108. var defaults = {};
  109. self.default = function (key, value) {
  110. if (typeof key === 'object') {
  111. Object.keys(key).forEach(function (k) {
  112. self.default(k, key[k]);
  113. });
  114. }
  115. else {
  116. defaults[key] = value;
  117. }
  118. return self;
  119. };
  120. var descriptions = {};
  121. self.describe = function (key, desc) {
  122. if (typeof key === 'object') {
  123. Object.keys(key).forEach(function (k) {
  124. self.describe(k, key[k]);
  125. });
  126. }
  127. else {
  128. descriptions[key] = desc;
  129. }
  130. return self;
  131. };
  132. self.parse = function (args) {
  133. return Argv(args).argv;
  134. };
  135. self.option = self.options = function (key, opt) {
  136. if (typeof key === 'object') {
  137. Object.keys(key).forEach(function (k) {
  138. self.options(k, key[k]);
  139. });
  140. }
  141. else {
  142. if (opt.alias) self.alias(key, opt.alias);
  143. if (opt.demand) self.demand(key);
  144. if (typeof opt.default !== 'undefined') {
  145. self.default(key, opt.default);
  146. }
  147. if (opt.boolean || opt.type === 'boolean') {
  148. self.boolean(key);
  149. }
  150. if (opt.string || opt.type === 'string') {
  151. self.string(key);
  152. }
  153. var desc = opt.describe || opt.description || opt.desc;
  154. if (desc) {
  155. self.describe(key, desc);
  156. }
  157. }
  158. return self;
  159. };
  160. var wrap = null;
  161. self.wrap = function (cols) {
  162. wrap = cols;
  163. return self;
  164. };
  165. self.showHelp = function (fn) {
  166. if (!fn) fn = console.error;
  167. fn(self.help());
  168. };
  169. self.help = function () {
  170. var keys = Object.keys(
  171. Object.keys(descriptions)
  172. .concat(Object.keys(demanded))
  173. .concat(Object.keys(defaults))
  174. .reduce(function (acc, key) {
  175. if (key !== '_') acc[key] = true;
  176. return acc;
  177. }, {})
  178. );
  179. var help = keys.length ? [ 'Options:' ] : [];
  180. if (usage) {
  181. help.unshift(usage.replace(/\$0/g, self.$0), '');
  182. }
  183. var switches = keys.reduce(function (acc, key) {
  184. acc[key] = [ key ].concat(aliases[key] || [])
  185. .map(function (sw) {
  186. return (sw.length > 1 ? '--' : '-') + sw
  187. })
  188. .join(', ')
  189. ;
  190. return acc;
  191. }, {});
  192. var switchlen = longest(Object.keys(switches).map(function (s) {
  193. return switches[s] || '';
  194. }));
  195. var desclen = longest(Object.keys(descriptions).map(function (d) {
  196. return descriptions[d] || '';
  197. }));
  198. keys.forEach(function (key) {
  199. var kswitch = switches[key];
  200. var desc = descriptions[key] || '';
  201. if (wrap) {
  202. desc = wordwrap(switchlen + 4, wrap)(desc)
  203. .slice(switchlen + 4)
  204. ;
  205. }
  206. var spadding = new Array(
  207. Math.max(switchlen - kswitch.length + 3, 0)
  208. ).join(' ');
  209. var dpadding = new Array(
  210. Math.max(desclen - desc.length + 1, 0)
  211. ).join(' ');
  212. var type = null;
  213. if (flags.bools[key]) type = '[boolean]';
  214. if (flags.strings[key]) type = '[string]';
  215. if (!wrap && dpadding.length > 0) {
  216. desc += dpadding;
  217. }
  218. var prelude = ' ' + kswitch + spadding;
  219. var extra = [
  220. type,
  221. demanded[key]
  222. ? '[required]'
  223. : null
  224. ,
  225. defaults[key] !== undefined
  226. ? '[default: ' + JSON.stringify(defaults[key]) + ']'
  227. : null
  228. ,
  229. ].filter(Boolean).join(' ');
  230. var body = [ desc, extra ].filter(Boolean).join(' ');
  231. if (wrap) {
  232. var dlines = desc.split('\n');
  233. var dlen = dlines.slice(-1)[0].length
  234. + (dlines.length === 1 ? prelude.length : 0)
  235. body = desc + (dlen + extra.length > wrap - 2
  236. ? '\n'
  237. + new Array(wrap - extra.length + 1).join(' ')
  238. + extra
  239. : new Array(wrap - extra.length - dlen + 1).join(' ')
  240. + extra
  241. );
  242. }
  243. help.push(prelude + body);
  244. });
  245. help.push('');
  246. return help.join('\n');
  247. };
  248. Object.defineProperty(self, 'argv', {
  249. get : parseArgs,
  250. enumerable : true,
  251. });
  252. function parseArgs () {
  253. var argv = { _ : [], $0 : self.$0 };
  254. Object.keys(flags.bools).forEach(function (key) {
  255. setArg(key, defaults[key] || false);
  256. });
  257. function setArg (key, val) {
  258. var num = Number(val);
  259. var value = typeof val !== 'string' || isNaN(num) ? val : num;
  260. if (flags.strings[key]) value = val;
  261. setKey(argv, key.split('.'), value);
  262. (aliases[key] || []).forEach(function (x) {
  263. argv[x] = argv[key];
  264. });
  265. }
  266. for (var i = 0; i < args.length; i++) {
  267. var arg = args[i];
  268. if (arg === '--') {
  269. argv._.push.apply(argv._, args.slice(i + 1));
  270. break;
  271. }
  272. else if (arg.match(/^--.+=/)) {
  273. // Using [\s\S] instead of . because js doesn't support the
  274. // 'dotall' regex modifier. See:
  275. // http://stackoverflow.com/a/1068308/13216
  276. var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
  277. setArg(m[1], m[2]);
  278. }
  279. else if (arg.match(/^--no-.+/)) {
  280. var key = arg.match(/^--no-(.+)/)[1];
  281. setArg(key, false);
  282. }
  283. else if (arg.match(/^--.+/)) {
  284. var key = arg.match(/^--(.+)/)[1];
  285. var next = args[i + 1];
  286. if (next !== undefined && !next.match(/^-/)
  287. && !flags.bools[key]
  288. && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
  289. setArg(key, next);
  290. i++;
  291. }
  292. else if (/^(true|false)$/.test(next)) {
  293. setArg(key, next === 'true');
  294. i++;
  295. }
  296. else {
  297. setArg(key, true);
  298. }
  299. }
  300. else if (arg.match(/^-[^-]+/)) {
  301. var letters = arg.slice(1,-1).split('');
  302. var broken = false;
  303. for (var j = 0; j < letters.length; j++) {
  304. if (letters[j+1] && letters[j+1].match(/\W/)) {
  305. setArg(letters[j], arg.slice(j+2));
  306. broken = true;
  307. break;
  308. }
  309. else {
  310. setArg(letters[j], true);
  311. }
  312. }
  313. if (!broken) {
  314. var key = arg.slice(-1)[0];
  315. if (args[i+1] && !args[i+1].match(/^-/)
  316. && !flags.bools[key]
  317. && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
  318. setArg(key, args[i+1]);
  319. i++;
  320. }
  321. else if (args[i+1] && /true|false/.test(args[i+1])) {
  322. setArg(key, args[i+1] === 'true');
  323. i++;
  324. }
  325. else {
  326. setArg(key, true);
  327. }
  328. }
  329. }
  330. else {
  331. var n = Number(arg);
  332. argv._.push(flags.strings['_'] || isNaN(n) ? arg : n);
  333. }
  334. }
  335. Object.keys(defaults).forEach(function (key) {
  336. if (!(key in argv)) {
  337. argv[key] = defaults[key];
  338. if (key in aliases) {
  339. argv[aliases[key]] = defaults[key];
  340. }
  341. }
  342. });
  343. if (demanded._ && argv._.length < demanded._) {
  344. fail('Not enough non-option arguments: got '
  345. + argv._.length + ', need at least ' + demanded._
  346. );
  347. }
  348. var missing = [];
  349. Object.keys(demanded).forEach(function (key) {
  350. if (!argv[key]) missing.push(key);
  351. });
  352. if (missing.length) {
  353. fail('Missing required arguments: ' + missing.join(', '));
  354. }
  355. checks.forEach(function (f) {
  356. try {
  357. if (f(argv) === false) {
  358. fail('Argument check failed: ' + f.toString());
  359. }
  360. }
  361. catch (err) {
  362. fail(err)
  363. }
  364. });
  365. return argv;
  366. }
  367. function longest (xs) {
  368. return Math.max.apply(
  369. null,
  370. xs.map(function (x) { return x.length })
  371. );
  372. }
  373. return self;
  374. };
  375. // rebase an absolute path to a relative one with respect to a base directory
  376. // exported for tests
  377. exports.rebase = rebase;
  378. function rebase (base, dir) {
  379. var ds = path.normalize(dir).split('/').slice(1);
  380. var bs = path.normalize(base).split('/').slice(1);
  381. for (var i = 0; ds[i] && ds[i] == bs[i]; i++);
  382. ds.splice(0, i); bs.splice(0, i);
  383. var p = path.normalize(
  384. bs.map(function () { return '..' }).concat(ds).join('/')
  385. ).replace(/\/$/,'').replace(/^$/, '.');
  386. return p.match(/^[.\/]/) ? p : './' + p;
  387. };
  388. function setKey (obj, keys, value) {
  389. var o = obj;
  390. keys.slice(0,-1).forEach(function (key) {
  391. if (o[key] === undefined) o[key] = {};
  392. o = o[key];
  393. });
  394. var key = keys[keys.length - 1];
  395. if (o[key] === undefined || typeof o[key] === 'boolean') {
  396. o[key] = value;
  397. }
  398. else if (Array.isArray(o[key])) {
  399. o[key].push(value);
  400. }
  401. else {
  402. o[key] = [ o[key], value ];
  403. }
  404. }