utils.js 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. 'use strict';
  2. const {
  3. ArrayPrototypeFind,
  4. ObjectEntries,
  5. ObjectPrototypeHasOwnProperty: ObjectHasOwn,
  6. StringPrototypeCharAt,
  7. StringPrototypeIncludes,
  8. StringPrototypeStartsWith,
  9. } = require('./internal/primordials');
  10. const {
  11. validateObject,
  12. } = require('./internal/validators');
  13. // These are internal utilities to make the parsing logic easier to read, and
  14. // add lots of detail for the curious. They are in a separate file to allow
  15. // unit testing, although that is not essential (this could be rolled into
  16. // main file and just tested implicitly via API).
  17. //
  18. // These routines are for internal use, not for export to client.
  19. /**
  20. * Return the named property, but only if it is an own property.
  21. */
  22. function objectGetOwn(obj, prop) {
  23. if (ObjectHasOwn(obj, prop))
  24. return obj[prop];
  25. }
  26. /**
  27. * Return the named options property, but only if it is an own property.
  28. */
  29. function optionsGetOwn(options, longOption, prop) {
  30. if (ObjectHasOwn(options, longOption))
  31. return objectGetOwn(options[longOption], prop);
  32. }
  33. /**
  34. * Determines if the argument may be used as an option value.
  35. * @example
  36. * isOptionValue('V') // returns true
  37. * isOptionValue('-v') // returns true (greedy)
  38. * isOptionValue('--foo') // returns true (greedy)
  39. * isOptionValue(undefined) // returns false
  40. */
  41. function isOptionValue(value) {
  42. if (value == null) return false;
  43. // Open Group Utility Conventions are that an option-argument
  44. // is the argument after the option, and may start with a dash.
  45. return true; // greedy!
  46. }
  47. /**
  48. * Detect whether there is possible confusion and user may have omitted
  49. * the option argument, like `--port --verbose` when `port` of type:string.
  50. * In strict mode we throw errors if value is option-like.
  51. */
  52. function isOptionLikeValue(value) {
  53. if (value == null) return false;
  54. return value.length > 1 && StringPrototypeCharAt(value, 0) === '-';
  55. }
  56. /**
  57. * Determines if `arg` is just a short option.
  58. * @example '-f'
  59. */
  60. function isLoneShortOption(arg) {
  61. return arg.length === 2 &&
  62. StringPrototypeCharAt(arg, 0) === '-' &&
  63. StringPrototypeCharAt(arg, 1) !== '-';
  64. }
  65. /**
  66. * Determines if `arg` is a lone long option.
  67. * @example
  68. * isLoneLongOption('a') // returns false
  69. * isLoneLongOption('-a') // returns false
  70. * isLoneLongOption('--foo') // returns true
  71. * isLoneLongOption('--foo=bar') // returns false
  72. */
  73. function isLoneLongOption(arg) {
  74. return arg.length > 2 &&
  75. StringPrototypeStartsWith(arg, '--') &&
  76. !StringPrototypeIncludes(arg, '=', 3);
  77. }
  78. /**
  79. * Determines if `arg` is a long option and value in the same argument.
  80. * @example
  81. * isLongOptionAndValue('--foo') // returns false
  82. * isLongOptionAndValue('--foo=bar') // returns true
  83. */
  84. function isLongOptionAndValue(arg) {
  85. return arg.length > 2 &&
  86. StringPrototypeStartsWith(arg, '--') &&
  87. StringPrototypeIncludes(arg, '=', 3);
  88. }
  89. /**
  90. * Determines if `arg` is a short option group.
  91. *
  92. * See Guideline 5 of the [Open Group Utility Conventions](https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html).
  93. * One or more options without option-arguments, followed by at most one
  94. * option that takes an option-argument, should be accepted when grouped
  95. * behind one '-' delimiter.
  96. * @example
  97. * isShortOptionGroup('-a', {}) // returns false
  98. * isShortOptionGroup('-ab', {}) // returns true
  99. * // -fb is an option and a value, not a short option group
  100. * isShortOptionGroup('-fb', {
  101. * options: { f: { type: 'string' } }
  102. * }) // returns false
  103. * isShortOptionGroup('-bf', {
  104. * options: { f: { type: 'string' } }
  105. * }) // returns true
  106. * // -bfb is an edge case, return true and caller sorts it out
  107. * isShortOptionGroup('-bfb', {
  108. * options: { f: { type: 'string' } }
  109. * }) // returns true
  110. */
  111. function isShortOptionGroup(arg, options) {
  112. if (arg.length <= 2) return false;
  113. if (StringPrototypeCharAt(arg, 0) !== '-') return false;
  114. if (StringPrototypeCharAt(arg, 1) === '-') return false;
  115. const firstShort = StringPrototypeCharAt(arg, 1);
  116. const longOption = findLongOptionForShort(firstShort, options);
  117. return optionsGetOwn(options, longOption, 'type') !== 'string';
  118. }
  119. /**
  120. * Determine if arg is a short string option followed by its value.
  121. * @example
  122. * isShortOptionAndValue('-a', {}); // returns false
  123. * isShortOptionAndValue('-ab', {}); // returns false
  124. * isShortOptionAndValue('-fFILE', {
  125. * options: { foo: { short: 'f', type: 'string' }}
  126. * }) // returns true
  127. */
  128. function isShortOptionAndValue(arg, options) {
  129. validateObject(options, 'options');
  130. if (arg.length <= 2) return false;
  131. if (StringPrototypeCharAt(arg, 0) !== '-') return false;
  132. if (StringPrototypeCharAt(arg, 1) === '-') return false;
  133. const shortOption = StringPrototypeCharAt(arg, 1);
  134. const longOption = findLongOptionForShort(shortOption, options);
  135. return optionsGetOwn(options, longOption, 'type') === 'string';
  136. }
  137. /**
  138. * Find the long option associated with a short option. Looks for a configured
  139. * `short` and returns the short option itself if a long option is not found.
  140. * @example
  141. * findLongOptionForShort('a', {}) // returns 'a'
  142. * findLongOptionForShort('b', {
  143. * options: { bar: { short: 'b' } }
  144. * }) // returns 'bar'
  145. */
  146. function findLongOptionForShort(shortOption, options) {
  147. validateObject(options, 'options');
  148. const longOptionEntry = ArrayPrototypeFind(
  149. ObjectEntries(options),
  150. ({ 1: optionConfig }) => objectGetOwn(optionConfig, 'short') === shortOption
  151. );
  152. return longOptionEntry?.[0] ?? shortOption;
  153. }
  154. /**
  155. * Check if the given option includes a default value
  156. * and that option has not been set by the input args.
  157. *
  158. * @param {string} longOption - long option name e.g. 'foo'
  159. * @param {object} optionConfig - the option configuration properties
  160. * @param {object} values - option values returned in `values` by parseArgs
  161. */
  162. function useDefaultValueOption(longOption, optionConfig, values) {
  163. return objectGetOwn(optionConfig, 'default') !== undefined &&
  164. values[longOption] === undefined;
  165. }
  166. module.exports = {
  167. findLongOptionForShort,
  168. isLoneLongOption,
  169. isLoneShortOption,
  170. isLongOptionAndValue,
  171. isOptionValue,
  172. isOptionLikeValue,
  173. isShortOptionAndValue,
  174. isShortOptionGroup,
  175. useDefaultValueOption,
  176. objectGetOwn,
  177. optionsGetOwn,
  178. };