22
33const {
44 ArrayPrototypeConcat,
5- ArrayPrototypeFind,
65 ArrayPrototypeForEach,
6+ ArrayPrototypeShift,
77 ArrayPrototypeSlice,
8- ArrayPrototypeSplice,
98 ArrayPrototypePush,
109 ObjectHasOwn,
1110 ObjectEntries,
1211 StringPrototypeCharAt,
1312 StringPrototypeIncludes,
1413 StringPrototypeIndexOf,
1514 StringPrototypeSlice,
16- StringPrototypeStartsWith,
1715} = require ( './primordials' ) ;
1816
1917const {
@@ -24,6 +22,16 @@ const {
2422 validateBoolean,
2523} = require ( './validators' ) ;
2624
25+ const {
26+ findLongOptionForShort,
27+ isLoneLongOption,
28+ isLoneShortOption,
29+ isLongOptionAndValue,
30+ isOptionValue,
31+ isShortOptionAndValue,
32+ isShortOptionGroup
33+ } = require ( './utils' ) ;
34+
2735function getMainArgs ( ) {
2836 // This function is a placeholder for proposed process.mainArgs.
2937 // Work out where to slice process.argv for user supplied arguments.
@@ -116,86 +124,89 @@ const parseArgs = ({
116124 positionals : [ ]
117125 } ;
118126
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+ }
147142
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+ }
149155
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+ }
154177
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+ }
156186
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 ) ;
160194 }
195+ storeOptionValue ( options , longOption , optionValue , result ) ;
196+ continue ;
197+ }
161198
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 ;
196206 }
197207
198- pos ++ ;
208+ // Anything left is a positional
209+ ArrayPrototypePush ( result . positionals , arg ) ;
199210 }
200211
201212 return result ;
0 commit comments