index.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  1. module.exports = function (args, opts) {
  2. if (!opts) opts = {};
  3. var flags = { bools : {}, strings : {} };
  4. [].concat(opts['boolean']).filter(Boolean).forEach(function (key) {
  5. flags.bools[key] = true;
  6. });
  7. [].concat(opts.string).filter(Boolean).forEach(function (key) {
  8. flags.strings[key] = true;
  9. });
  10. var aliases = {};
  11. Object.keys(opts.alias || {}).forEach(function (key) {
  12. aliases[key] = [].concat(opts.alias[key]);
  13. aliases[key].forEach(function (x) {
  14. aliases[x] = [key].concat(aliases[key].filter(function (y) {
  15. return x !== y;
  16. }));
  17. });
  18. });
  19. var defaults = opts['default'] || {};
  20. var argv = { _ : [] };
  21. Object.keys(flags.bools).forEach(function (key) {
  22. setArg(key, defaults[key] === undefined ? false : defaults[key]);
  23. });
  24. var notFlags = [];
  25. if (args.indexOf('--') !== -1) {
  26. notFlags = args.slice(args.indexOf('--')+1);
  27. args = args.slice(0, args.indexOf('--'));
  28. }
  29. function setArg (key, val) {
  30. var value = !flags.strings[key] && isNumber(val)
  31. ? Number(val) : val
  32. ;
  33. setKey(argv, key.split('.'), value);
  34. (aliases[key] || []).forEach(function (x) {
  35. setKey(argv, x.split('.'), value);
  36. });
  37. }
  38. for (var i = 0; i < args.length; i++) {
  39. var arg = args[i];
  40. if (/^--.+=/.test(arg)) {
  41. // Using [\s\S] instead of . because js doesn't support the
  42. // 'dotall' regex modifier. See:
  43. // http://stackoverflow.com/a/1068308/13216
  44. var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
  45. setArg(m[1], m[2]);
  46. }
  47. else if (/^--no-.+/.test(arg)) {
  48. var key = arg.match(/^--no-(.+)/)[1];
  49. setArg(key, false);
  50. }
  51. else if (/^--.+/.test(arg)) {
  52. var key = arg.match(/^--(.+)/)[1];
  53. var next = args[i + 1];
  54. if (next !== undefined && !/^-/.test(next)
  55. && !flags.bools[key]
  56. && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
  57. setArg(key, next);
  58. i++;
  59. }
  60. else if (/^(true|false)$/.test(next)) {
  61. setArg(key, next === 'true');
  62. i++;
  63. }
  64. else {
  65. setArg(key, flags.strings[key] ? '' : true);
  66. }
  67. }
  68. else if (/^-[^-]+/.test(arg)) {
  69. var letters = arg.slice(1,-1).split('');
  70. var broken = false;
  71. for (var j = 0; j < letters.length; j++) {
  72. var next = arg.slice(j+2);
  73. if (next === '-') {
  74. setArg(letters[j], next)
  75. continue;
  76. }
  77. if (/[A-Za-z]/.test(letters[j])
  78. && /-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) {
  79. setArg(letters[j], next);
  80. broken = true;
  81. break;
  82. }
  83. if (letters[j+1] && letters[j+1].match(/\W/)) {
  84. setArg(letters[j], arg.slice(j+2));
  85. broken = true;
  86. break;
  87. }
  88. else {
  89. setArg(letters[j], flags.strings[letters[j]] ? '' : true);
  90. }
  91. }
  92. var key = arg.slice(-1)[0];
  93. if (!broken && key !== '-') {
  94. if (args[i+1] && !/^(-|--)[^-]/.test(args[i+1])
  95. && !flags.bools[key]
  96. && (aliases[key] ? !flags.bools[aliases[key]] : true)) {
  97. setArg(key, args[i+1]);
  98. i++;
  99. }
  100. else if (args[i+1] && /true|false/.test(args[i+1])) {
  101. setArg(key, args[i+1] === 'true');
  102. i++;
  103. }
  104. else {
  105. setArg(key, flags.strings[key] ? '' : true);
  106. }
  107. }
  108. }
  109. else {
  110. argv._.push(
  111. flags.strings['_'] || !isNumber(arg) ? arg : Number(arg)
  112. );
  113. }
  114. }
  115. Object.keys(defaults).forEach(function (key) {
  116. if (!hasKey(argv, key.split('.'))) {
  117. setKey(argv, key.split('.'), defaults[key]);
  118. (aliases[key] || []).forEach(function (x) {
  119. setKey(argv, x.split('.'), defaults[key]);
  120. });
  121. }
  122. });
  123. notFlags.forEach(function(key) {
  124. argv._.push(key);
  125. });
  126. return argv;
  127. };
  128. function hasKey (obj, keys) {
  129. var o = obj;
  130. keys.slice(0,-1).forEach(function (key) {
  131. o = (o[key] || {});
  132. });
  133. var key = keys[keys.length - 1];
  134. return key in o;
  135. }
  136. function setKey (obj, keys, value) {
  137. var o = obj;
  138. keys.slice(0,-1).forEach(function (key) {
  139. if (o[key] === undefined) o[key] = {};
  140. o = o[key];
  141. });
  142. var key = keys[keys.length - 1];
  143. if (o[key] === undefined || typeof o[key] === 'boolean') {
  144. o[key] = value;
  145. }
  146. else if (Array.isArray(o[key])) {
  147. o[key].push(value);
  148. }
  149. else {
  150. o[key] = [ o[key], value ];
  151. }
  152. }
  153. function isNumber (x) {
  154. if (typeof x === 'number') return true;
  155. if (/^0x[0-9a-f]+$/i.test(x)) return true;
  156. return /^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/.test(x);
  157. }
  158. function longest (xs) {
  159. return Math.max.apply(null, xs.map(function (x) { return x.length }));
  160. }