getOpt.js 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  1. /*
  2. * Intro
  3. *
  4. * Very simple implementation of an option parser. It supports long and short
  5. * options, bundled options (-avd) and options with values. It receives an
  6. * argument object (argObj) describing the options, their type (not to be
  7. * confused with their value type, i.e Integer, String, etc), a default value
  8. * (if any), and, possibly, functions for value validation and convertion. It
  9. * returns an object contanining key-value pairs of the defined long options. If
  10. * a option of type 'value' is not specified, and if there is no default value,
  11. * it will be set no null.
  12. *
  13. * This 'getOpt' also forces the definition of long option names, this is in
  14. * attempt to make the returned option object more readable. But nothing is
  15. * stopping the user from defining single letter long option names (i.e '--l').
  16. *
  17. * Brief example
  18. *
  19. * Example of an argObj:
  20. *
  21. * {
  22. * 'has-value':
  23. * {
  24. * type: 'value',
  25. * short: 'h',
  26. * convert: v => parseInt(v),
  27. * validate: v => v > 1
  28. * }
  29. * 'verbose': {
  30. * type: 'flag',
  31. * short: 'v'
  32. * }
  33. * }
  34. *
  35. * If a command with the options of '--has-value 5 --verbose' or '-h 5 -v' was
  36. * supplied, it would generate and the options object: { 'has-value': 5,
  37. * verbose: true }.
  38. *
  39. * Argument Object
  40. *
  41. * type
  42. *
  43. * There are two types of arguments supported: arguments with user defined
  44. * values ('type: value') and arguments that act as switches ('type: flag') and
  45. * carry no user specified value. They have two strategies for parsing: type
  46. * 'value' consumes the next immediate argument; and type 'flag' doesn't consume
  47. * anything on the argument parsing loop.
  48. *
  49. * short
  50. *
  51. * A short version of the option, meant to be used with a single dash ('-')
  52. * character. Short options can be bundled together ('-abc' or '-p3000'), but
  53. * only if they are all of type 'flag', or if the there is a single 'value' type
  54. * option on the end ('-abp3000').
  55. *
  56. * default
  57. *
  58. * Default value for option in case none is specified.
  59. *
  60. * convert
  61. *
  62. * A optional function to perform any required operations on the extracted
  63. * value. Its executed before the 'validate' function.
  64. *
  65. * validate
  66. *
  67. * An optional function that tests the value of value. Its exectued after the
  68. * 'convert' function and must return a falsy or truthy value.
  69. */
  70. "use strict";
  71. export function getOpt(argObj) {
  72. let argv = process.argv.slice(2)
  73. let opts = {};
  74. let notOpts = [];
  75. let arg;
  76. while ((arg = argv.shift())) {
  77. // Skip non-option args
  78. if (!arg.startsWith('-')) {
  79. notOpts.push(arg);
  80. continue;
  81. }
  82. // End of options found
  83. if (arg == '--') break;
  84. // Remove dashes from option
  85. let argOpt = arg.slice(1);
  86. if (argOpt[0] == '-')
  87. argOpt = argKey.slice(1);
  88. try {
  89. // Split possible bundled options
  90. let argKey;
  91. let found = false;
  92. for (let i = 0; i < argOpt.length; i++) {
  93. const c = argOpt[i];
  94. for (const k of Object.keys(argObj)) {
  95. if (argObj[k].short == c) {
  96. if (!argKey) argKey = k;
  97. else argv.unshift(c);
  98. found = true;
  99. }
  100. }
  101. if (!found) {
  102. argv.unshift(argOpt.slice(i, argObj.length));
  103. break;
  104. }
  105. found = false;
  106. }
  107. let type = argObj[argKey].type;
  108. switch (type) {
  109. case 'value':
  110. let val = argv.shift();
  111. const validateFunc = argObj[argKey].validate;
  112. const convertFunc = argObj[argKey].convert;
  113. if (convertFunc) val = convertFunc(val)
  114. if (validateFunc) {
  115. if (!validateFunc(val)) throw '';
  116. }
  117. opts[argKey] = val;
  118. break;
  119. case 'flag':
  120. opts[argOpt] = true;
  121. argv.shift();
  122. break;
  123. default:
  124. die(`Invalid string '${type}' for getOpt`);
  125. }
  126. } catch (e) {
  127. die(`Invalid argument '${arg}'`);
  128. }
  129. }
  130. if (notOpts.length >= 1)
  131. process.argv = process.argv.slice(0, 2).concat(notOpts);
  132. Object.keys(argObj).forEach(k => {
  133. if (!opts[k]) {
  134. if (argObj[k].default)
  135. opts[k] = argObj[k].default;
  136. else opts[k] = null;
  137. }
  138. });
  139. return opts;
  140. }