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