@@ -16,12 +16,11 @@ const colorsUtil = require("./colors");
1616// S | string array
1717
1818/** Parses the specified command line arguments according to the given configuration. */
19- function parse ( argv , config ) {
19+ function parse ( argv , config , propagateDefaults = true ) {
2020 var options = { } ;
2121 var unknown = [ ] ;
2222 var args = [ ] ;
2323 var trailing = [ ] ;
24- var provided = new Set ( ) ;
2524
2625 // make an alias map and initialize defaults
2726 var aliases = { } ;
@@ -54,13 +53,15 @@ function parse(argv, config) {
5453 else { args . push ( arg ) ; continue ; } // argument
5554 }
5655 if ( option ) {
57- if ( option . type == null || option . type === "b" ) {
58- options [ key ] = true ; // flag
59- provided . add ( key ) ;
56+ if ( option . value ) {
57+ // alias setting fixed values
58+ Object . keys ( option . value ) . forEach ( k => options [ k ] = option . value [ k ] ) ;
59+ } else if ( option . type == null || option . type === "b" ) {
60+ // boolean flag not taking a value
61+ options [ key ] = true ;
6062 } else {
61- // the argument was provided
62- if ( i + 1 < argv . length && argv [ i + 1 ] . charCodeAt ( 0 ) != 45 ) { // present
63- provided . add ( key ) ;
63+ if ( i + 1 < argv . length && argv [ i + 1 ] . charCodeAt ( 0 ) != 45 ) {
64+ // non-boolean with given value
6465 switch ( option . type ) {
6566 case "i" : options [ key ] = parseInt ( argv [ ++ i ] , 10 ) ; break ;
6667 case "I" : options [ key ] = ( options [ key ] || [ ] ) . concat ( parseInt ( argv [ ++ i ] , 10 ) ) ; break ;
@@ -70,7 +71,8 @@ function parse(argv, config) {
7071 case "S" : options [ key ] = ( options [ key ] || [ ] ) . concat ( argv [ ++ i ] . split ( "," ) ) ; break ;
7172 default : unknown . push ( arg ) ; -- i ;
7273 }
73- } else { // omitted
74+ } else {
75+ // non-boolean with omitted value
7476 switch ( option . type ) {
7577 case "i" :
7678 case "f" : options [ key ] = option . default || 0 ; break ;
@@ -82,12 +84,12 @@ function parse(argv, config) {
8284 }
8385 }
8486 }
85- if ( option . value ) Object . keys ( option . value ) . forEach ( k => options [ k ] = option . value [ k ] ) ;
8687 } else unknown . push ( arg ) ;
8788 }
8889 while ( i < k ) trailing . push ( argv [ i ++ ] ) ; // trailing
90+ if ( propagateDefaults ) addDefaults ( config , options ) ;
8991
90- return { options, unknown, arguments : args , trailing, provided } ;
92+ return { options, unknown, arguments : args , trailing } ;
9193}
9294
9395exports . parse = parse ;
@@ -138,3 +140,93 @@ function help(config, options) {
138140}
139141
140142exports . help = help ;
143+
144+ /** Sanitizes an option value to be a valid value of the option's type. */
145+ function sanitizeValue ( value , type ) {
146+ if ( value != null ) {
147+ switch ( type ) {
148+ case undefined :
149+ case "b" : return Boolean ( value ) ;
150+ case "i" : return Math . trunc ( value ) || 0 ;
151+ case "f" : return Number ( value ) || 0 ;
152+ case "s" : return String ( value ) ;
153+ case "I" : {
154+ if ( ! Array . isArray ( value ) ) value = [ value ] ;
155+ return value . map ( v => Math . trunc ( v ) || 0 ) ;
156+ }
157+ case "F" : {
158+ if ( ! Array . isArray ( value ) ) value = [ value ] ;
159+ return value . map ( v => Number ( v ) || 0 ) ;
160+ }
161+ case "S" : {
162+ if ( ! Array . isArray ( value ) ) value = [ value ] ;
163+ return value . map ( String ) ;
164+ }
165+ }
166+ }
167+ return undefined ;
168+ }
169+
170+ /** Merges two sets of options into one, preferring the current over the parent set. */
171+ function merge ( config , currentOptions , parentOptions ) {
172+ const mergedOptions = { } ;
173+ for ( const [ key , { type, mutuallyExclusive } ] of Object . entries ( config ) ) {
174+ let currentValue = sanitizeValue ( currentOptions [ key ] , type ) ;
175+ let parentValue = sanitizeValue ( parentOptions [ key ] , type ) ;
176+ if ( currentValue == null ) {
177+ if ( parentValue != null ) {
178+ // only parent value present
179+ if ( Array . isArray ( parentValue ) ) {
180+ let exclude ;
181+ if ( mutuallyExclusive != null && ( exclude = currentOptions [ mutuallyExclusive ] ) ) {
182+ mergedOptions [ key ] = parentValue . filter ( value => ! exclude . includes ( value ) ) ;
183+ } else {
184+ mergedOptions [ key ] = parentValue . slice ( ) ;
185+ }
186+ } else {
187+ mergedOptions [ key ] = parentValue ;
188+ }
189+ }
190+ } else if ( parentValue == null ) {
191+ // only current value present
192+ if ( Array . isArray ( currentValue ) ) {
193+ mergedOptions [ key ] = currentValue . slice ( ) ;
194+ } else {
195+ mergedOptions [ key ] = currentValue ;
196+ }
197+ } else {
198+ // both current and parent values present
199+ if ( Array . isArray ( currentValue ) ) {
200+ let exclude ;
201+ if ( mutuallyExclusive != null && ( exclude = currentOptions [ mutuallyExclusive ] ) ) {
202+ mergedOptions [ key ] = [
203+ ...currentValue ,
204+ ...parentValue . filter ( value => ! currentValue . includes ( value ) && ! exclude . includes ( value ) )
205+ ] ;
206+ } else {
207+ mergedOptions [ key ] = [
208+ ...currentValue ,
209+ ...parentValue . filter ( value => ! currentValue . includes ( value ) ) // dedup
210+ ] ;
211+ }
212+ } else {
213+ mergedOptions [ key ] = currentValue ;
214+ }
215+ }
216+ }
217+ return mergedOptions ;
218+ }
219+
220+ exports . merge = merge ;
221+
222+ /** Populates default values on a parsed options result. */
223+ function addDefaults ( config , options ) {
224+ for ( const [ key , { default : defaultValue } ] of Object . entries ( config ) ) {
225+ if ( options [ key ] == null && defaultValue != null ) {
226+ options [ key ] = defaultValue ;
227+ }
228+ }
229+ return options ;
230+ }
231+
232+ exports . addDefaults = addDefaults ;
0 commit comments