2
2
3
3
const {
4
4
ArrayPrototypeConcat,
5
- ArrayPrototypeFind,
6
5
ArrayPrototypeForEach,
6
+ ArrayPrototypeShift,
7
7
ArrayPrototypeSlice,
8
- ArrayPrototypeSplice,
9
8
ArrayPrototypePush,
10
9
ObjectHasOwn,
11
10
ObjectEntries,
12
11
StringPrototypeCharAt,
13
12
StringPrototypeIncludes,
14
13
StringPrototypeIndexOf,
15
14
StringPrototypeSlice,
16
- StringPrototypeStartsWith,
17
15
} = require ( './primordials' ) ;
18
16
19
17
const {
@@ -24,6 +22,16 @@ const {
24
22
validateBoolean,
25
23
} = require ( './validators' ) ;
26
24
25
+ const {
26
+ findLongOptionForShort,
27
+ isLoneLongOption,
28
+ isLoneShortOption,
29
+ isLongOptionAndValue,
30
+ isOptionValue,
31
+ isShortOptionAndValue,
32
+ isShortOptionGroup
33
+ } = require ( './utils' ) ;
34
+
27
35
function getMainArgs ( ) {
28
36
// This function is a placeholder for proposed process.mainArgs.
29
37
// Work out where to slice process.argv for user supplied arguments.
@@ -116,86 +124,89 @@ const parseArgs = ({
116
124
positionals : [ ]
117
125
} ;
118
126
119
- let pos = 0 ;
120
- while ( pos < args . length ) {
121
- let arg = args [ pos ] ;
122
-
123
- if ( StringPrototypeStartsWith ( arg , '-' ) ) {
124
- if ( arg === '-' ) {
125
- // '-' commonly used to represent stdin/stdout, treat as positional
126
- result . positionals = ArrayPrototypeConcat ( result . positionals , '-' ) ;
127
- ++ pos ;
128
- continue ;
129
- } else if ( arg === '--' ) {
130
- // Everything after a bare '--' is considered a positional argument
131
- // and is returned verbatim
132
- result . positionals = ArrayPrototypeConcat (
133
- result . positionals ,
134
- ArrayPrototypeSlice ( args , ++ pos )
135
- ) ;
136
- return result ;
137
- } else if ( StringPrototypeCharAt ( arg , 1 ) !== '-' ) {
138
- // Look for shortcodes: -fXzy and expand them to -f -X -z -y:
139
- if ( arg . length > 2 ) {
140
- for ( let i = 2 ; i < arg . length ; i ++ ) {
141
- const shortOption = StringPrototypeCharAt ( arg , i ) ;
142
- // Add 'i' to 'pos' such that short options are parsed in order
143
- // of definition:
144
- ArrayPrototypeSplice ( args , pos + ( i - 1 ) , 0 , `-${ shortOption } ` ) ;
145
- }
146
- }
127
+ let remainingArgs = ArrayPrototypeSlice ( args ) ;
128
+ while ( remainingArgs . length > 0 ) {
129
+ const arg = ArrayPrototypeShift ( remainingArgs ) ;
130
+ const nextArg = remainingArgs [ 0 ] ;
131
+
132
+ // Check if `arg` is an options terminator.
133
+ // Guideline 10 in https://pubs.opengroup.org/onlinepubs/9699919799/basedefs/V1_chap12.html
134
+ if ( arg === '--' ) {
135
+ // Everything after a bare '--' is considered a positional argument.
136
+ result . positionals = ArrayPrototypeConcat (
137
+ result . positionals ,
138
+ remainingArgs
139
+ ) ;
140
+ break ; // Finished processing args, leave while loop.
141
+ }
147
142
148
- arg = StringPrototypeCharAt ( arg , 1 ) ; // short
143
+ if ( isLoneShortOption ( arg ) ) {
144
+ // e.g. '-f'
145
+ const shortOption = StringPrototypeCharAt ( arg , 1 ) ;
146
+ const longOption = findLongOptionForShort ( shortOption , options ) ;
147
+ let optionValue ;
148
+ if ( options [ longOption ] ?. type === 'string' && isOptionValue ( nextArg ) ) {
149
+ // e.g. '-f', 'bar'
150
+ optionValue = ArrayPrototypeShift ( remainingArgs ) ;
151
+ }
152
+ storeOptionValue ( options , longOption , optionValue , result ) ;
153
+ continue ;
154
+ }
149
155
150
- const [ longOption ] = ArrayPrototypeFind (
151
- ObjectEntries ( options ) ,
152
- ( [ , optionConfig ] ) => optionConfig . short === arg
153
- ) || [ ] ;
156
+ if ( isShortOptionGroup ( arg , options ) ) {
157
+ // Expand -fXzy to -f -X -z -y
158
+ const expanded = [ ] ;
159
+ for ( let index = 1 ; index < arg . length ; index ++ ) {
160
+ const shortOption = StringPrototypeCharAt ( arg , index ) ;
161
+ const longOption = findLongOptionForShort ( shortOption , options ) ;
162
+ if ( options [ longOption ] ?. type !== 'string' ||
163
+ index === arg . length - 1 ) {
164
+ // Boolean option, or last short in group. Well formed.
165
+ ArrayPrototypePush ( expanded , `-${ shortOption } ` ) ;
166
+ } else {
167
+ // String option in middle. Yuck.
168
+ // ToDo: if strict then throw
169
+ // Expand -abfFILE to -a -b -fFILE
170
+ ArrayPrototypePush ( expanded , `-${ StringPrototypeSlice ( arg , index ) } ` ) ;
171
+ break ; // finished short group
172
+ }
173
+ }
174
+ remainingArgs = ArrayPrototypeConcat ( expanded , remainingArgs ) ;
175
+ continue ;
176
+ }
154
177
155
- arg = longOption ?? arg ;
178
+ if ( isShortOptionAndValue ( arg , options ) ) {
179
+ // e.g. -fFILE
180
+ const shortOption = StringPrototypeCharAt ( arg , 1 ) ;
181
+ const longOption = findLongOptionForShort ( shortOption , options ) ;
182
+ const optionValue = StringPrototypeSlice ( arg , 2 ) ;
183
+ storeOptionValue ( options , longOption , optionValue , result ) ;
184
+ continue ;
185
+ }
156
186
157
- // ToDo: later code tests for `=` in arg and wrong for shorts
158
- } else {
159
- arg = StringPrototypeSlice ( arg , 2 ) ; // remove leading --
187
+ if ( isLoneLongOption ( arg ) ) {
188
+ // e.g. '--foo'
189
+ const longOption = StringPrototypeSlice ( arg , 2 ) ;
190
+ let optionValue ;
191
+ if ( options [ longOption ] ?. type === 'string' && isOptionValue ( nextArg ) ) {
192
+ // e.g. '--foo', 'bar'
193
+ optionValue = ArrayPrototypeShift ( remainingArgs ) ;
160
194
}
195
+ storeOptionValue ( options , longOption , optionValue , result ) ;
196
+ continue ;
197
+ }
161
198
162
- if ( StringPrototypeIncludes ( arg , '=' ) ) {
163
- // Store option=value same way independent of `type: "string"` as:
164
- // - looks like a value, store as a value
165
- // - match the intention of the user
166
- // - preserve information for author to process further
167
- const index = StringPrototypeIndexOf ( arg , '=' ) ;
168
- storeOptionValue (
169
- options ,
170
- StringPrototypeSlice ( arg , 0 , index ) ,
171
- StringPrototypeSlice ( arg , index + 1 ) ,
172
- result ) ;
173
- } else if ( pos + 1 < args . length &&
174
- ! StringPrototypeStartsWith ( args [ pos + 1 ] , '-' )
175
- ) {
176
- // `type: "string"` option should also support setting values when '='
177
- // isn't used ie. both --foo=b and --foo b should work
178
-
179
- // If `type: "string"` option is specified, take next position argument
180
- // as value and then increment pos so that we don't re-evaluate that
181
- // arg, else set value as undefined ie. --foo b --bar c, after setting
182
- // b as the value for foo, evaluate --bar next and skip 'b'
183
- const val = options [ arg ] && options [ arg ] . type === 'string' ?
184
- args [ ++ pos ] :
185
- undefined ;
186
- storeOptionValue ( options , arg , val , result ) ;
187
- } else {
188
- // Cases when an arg is specified without a value, example
189
- // '--foo --bar' <- 'foo' and 'bar' flags should be set to true and
190
- // save value as undefined
191
- storeOptionValue ( options , arg , undefined , result ) ;
192
- }
193
- } else {
194
- // Arguments without a dash prefix are considered "positional"
195
- ArrayPrototypePush ( result . positionals , arg ) ;
199
+ if ( isLongOptionAndValue ( arg ) ) {
200
+ // e.g. --foo=bar
201
+ const index = StringPrototypeIndexOf ( arg , '=' ) ;
202
+ const longOption = StringPrototypeSlice ( arg , 2 , index ) ;
203
+ const optionValue = StringPrototypeSlice ( arg , index + 1 ) ;
204
+ storeOptionValue ( options , longOption , optionValue , result ) ;
205
+ continue ;
196
206
}
197
207
198
- pos ++ ;
208
+ // Anything left is a positional
209
+ ArrayPrototypePush ( result . positionals , arg ) ;
199
210
}
200
211
201
212
return result ;
0 commit comments