Page Menu
Home
c4science
Search
Configure Global Search
Log In
Files
F101843414
yargs-parser.js
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Subscribers
None
File Metadata
Details
File Info
Storage
Attached
Created
Fri, Feb 14, 07:23
Size
45 KB
Mime Type
text/x-java
Expires
Sun, Feb 16, 07:23 (2 d)
Engine
blob
Format
Raw Data
Handle
24240865
Attached To
rOACCT Open Access Compliance Check Tool (OACCT)
yargs-parser.js
View Options
/**
* @license
* Copyright (c) 2016, Contributors
* SPDX-License-Identifier: ISC
*/
import
{
tokenizeArgString
}
from
'./tokenize-arg-string.js'
;
import
{
DefaultValuesForTypeKey
}
from
'./yargs-parser-types.js'
;
import
{
camelCase
,
decamelize
,
looksLikeNumber
}
from
'./string-utils.js'
;
let
mixin
;
export
class
YargsParser
{
constructor
(
_mixin
)
{
mixin
=
_mixin
;
}
parse
(
argsInput
,
options
)
{
const
opts
=
Object
.
assign
({
alias
:
undefined
,
array
:
undefined
,
boolean
:
undefined
,
config
:
undefined
,
configObjects
:
undefined
,
configuration
:
undefined
,
coerce
:
undefined
,
count
:
undefined
,
default
:
undefined
,
envPrefix
:
undefined
,
narg
:
undefined
,
normalize
:
undefined
,
string
:
undefined
,
number
:
undefined
,
__
:
undefined
,
key
:
undefined
},
options
);
// allow a string argument to be passed in rather
// than an argv array.
const
args
=
tokenizeArgString
(
argsInput
);
// aliases might have transitive relationships, normalize this.
const
aliases
=
combineAliases
(
Object
.
assign
(
Object
.
create
(
null
),
opts
.
alias
));
const
configuration
=
Object
.
assign
({
'boolean-negation'
:
true
,
'camel-case-expansion'
:
true
,
'combine-arrays'
:
false
,
'dot-notation'
:
true
,
'duplicate-arguments-array'
:
true
,
'flatten-duplicate-arrays'
:
true
,
'greedy-arrays'
:
true
,
'halt-at-non-option'
:
false
,
'nargs-eats-options'
:
false
,
'negation-prefix'
:
'no-'
,
'parse-numbers'
:
true
,
'parse-positional-numbers'
:
true
,
'populate--'
:
false
,
'set-placeholder-key'
:
false
,
'short-option-groups'
:
true
,
'strip-aliased'
:
false
,
'strip-dashed'
:
false
,
'unknown-options-as-args'
:
false
},
opts
.
configuration
);
const
defaults
=
Object
.
assign
(
Object
.
create
(
null
),
opts
.
default
);
const
configObjects
=
opts
.
configObjects
||
[];
const
envPrefix
=
opts
.
envPrefix
;
const
notFlagsOption
=
configuration
[
'populate--'
];
const
notFlagsArgv
=
notFlagsOption
?
'--'
:
'_'
;
const
newAliases
=
Object
.
create
(
null
);
const
defaulted
=
Object
.
create
(
null
);
// allow a i18n handler to be passed in, default to a fake one (util.format).
const
__
=
opts
.
__
||
mixin
.
format
;
const
flags
=
{
aliases
:
Object
.
create
(
null
),
arrays
:
Object
.
create
(
null
),
bools
:
Object
.
create
(
null
),
strings
:
Object
.
create
(
null
),
numbers
:
Object
.
create
(
null
),
counts
:
Object
.
create
(
null
),
normalize
:
Object
.
create
(
null
),
configs
:
Object
.
create
(
null
),
nargs
:
Object
.
create
(
null
),
coercions
:
Object
.
create
(
null
),
keys
:
[]
};
const
negative
=
/^-([0-9]+(\.[0-9]+)?|\.[0-9]+)$/
;
const
negatedBoolean
=
new
RegExp
(
'^--'
+
configuration
[
'negation-prefix'
]
+
'(.+)'
);
[].
concat
(
opts
.
array
||
[]).
filter
(
Boolean
).
forEach
(
function
(
opt
)
{
const
key
=
typeof
opt
===
'object'
?
opt
.
key
:
opt
;
// assign to flags[bools|strings|numbers]
const
assignment
=
Object
.
keys
(
opt
).
map
(
function
(
key
)
{
const
arrayFlagKeys
=
{
boolean
:
'bools'
,
string
:
'strings'
,
number
:
'numbers'
};
return
arrayFlagKeys
[
key
];
}).
filter
(
Boolean
).
pop
();
// assign key to be coerced
if
(
assignment
)
{
flags
[
assignment
][
key
]
=
true
;
}
flags
.
arrays
[
key
]
=
true
;
flags
.
keys
.
push
(
key
);
});
[].
concat
(
opts
.
boolean
||
[]).
filter
(
Boolean
).
forEach
(
function
(
key
)
{
flags
.
bools
[
key
]
=
true
;
flags
.
keys
.
push
(
key
);
});
[].
concat
(
opts
.
string
||
[]).
filter
(
Boolean
).
forEach
(
function
(
key
)
{
flags
.
strings
[
key
]
=
true
;
flags
.
keys
.
push
(
key
);
});
[].
concat
(
opts
.
number
||
[]).
filter
(
Boolean
).
forEach
(
function
(
key
)
{
flags
.
numbers
[
key
]
=
true
;
flags
.
keys
.
push
(
key
);
});
[].
concat
(
opts
.
count
||
[]).
filter
(
Boolean
).
forEach
(
function
(
key
)
{
flags
.
counts
[
key
]
=
true
;
flags
.
keys
.
push
(
key
);
});
[].
concat
(
opts
.
normalize
||
[]).
filter
(
Boolean
).
forEach
(
function
(
key
)
{
flags
.
normalize
[
key
]
=
true
;
flags
.
keys
.
push
(
key
);
});
if
(
typeof
opts
.
narg
===
'object'
)
{
Object
.
entries
(
opts
.
narg
).
forEach
(([
key
,
value
])
=>
{
if
(
typeof
value
===
'number'
)
{
flags
.
nargs
[
key
]
=
value
;
flags
.
keys
.
push
(
key
);
}
});
}
if
(
typeof
opts
.
coerce
===
'object'
)
{
Object
.
entries
(
opts
.
coerce
).
forEach
(([
key
,
value
])
=>
{
if
(
typeof
value
===
'function'
)
{
flags
.
coercions
[
key
]
=
value
;
flags
.
keys
.
push
(
key
);
}
});
}
if
(
typeof
opts
.
config
!==
'undefined'
)
{
if
(
Array
.
isArray
(
opts
.
config
)
||
typeof
opts
.
config
===
'string'
)
{
;
[].
concat
(
opts
.
config
).
filter
(
Boolean
).
forEach
(
function
(
key
)
{
flags
.
configs
[
key
]
=
true
;
});
}
else
if
(
typeof
opts
.
config
===
'object'
)
{
Object
.
entries
(
opts
.
config
).
forEach
(([
key
,
value
])
=>
{
if
(
typeof
value
===
'boolean'
||
typeof
value
===
'function'
)
{
flags
.
configs
[
key
]
=
value
;
}
});
}
}
// create a lookup table that takes into account all
// combinations of aliases: {f: ['foo'], foo: ['f']}
extendAliases
(
opts
.
key
,
aliases
,
opts
.
default
,
flags
.
arrays
);
// apply default values to all aliases.
Object
.
keys
(
defaults
).
forEach
(
function
(
key
)
{
(
flags
.
aliases
[
key
]
||
[]).
forEach
(
function
(
alias
)
{
defaults
[
alias
]
=
defaults
[
key
];
});
});
let
error
=
null
;
checkConfiguration
();
let
notFlags
=
[];
const
argv
=
Object
.
assign
(
Object
.
create
(
null
),
{
_
:
[]
});
// TODO(bcoe): for the first pass at removing object prototype we didn't
// remove all prototypes from objects returned by this API, we might want
// to gradually move towards doing so.
const
argvReturn
=
{};
for
(
let
i
=
0
;
i
<
args
.
length
;
i
++
)
{
const
arg
=
args
[
i
];
const
truncatedArg
=
arg
.
replace
(
/^-{3,}/
,
'---'
);
let
broken
;
let
key
;
let
letters
;
let
m
;
let
next
;
let
value
;
// any unknown option (except for end-of-options, "--")
if
(
arg
!==
'--'
&&
isUnknownOptionAsArg
(
arg
))
{
pushPositional
(
arg
);
// ---, ---=, ----, etc,
}
else
if
(
truncatedArg
.
match
(
/---+(=|$)/
))
{
// options without key name are invalid.
pushPositional
(
arg
);
continue
;
// -- separated by =
}
else
if
(
arg
.
match
(
/^--.+=/
)
||
(
!
configuration
[
'short-option-groups'
]
&&
arg
.
match
(
/^-.+=/
)))
{
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
m
=
arg
.
match
(
/^--?([^=]+)=([\s\S]*)$/
);
// arrays format = '--f=a b c'
if
(
m
!==
null
&&
Array
.
isArray
(
m
)
&&
m
.
length
>=
3
)
{
if
(
checkAllAliases
(
m
[
1
],
flags
.
arrays
))
{
i
=
eatArray
(
i
,
m
[
1
],
args
,
m
[
2
]);
}
else
if
(
checkAllAliases
(
m
[
1
],
flags
.
nargs
)
!==
false
)
{
// nargs format = '--f=monkey washing cat'
i
=
eatNargs
(
i
,
m
[
1
],
args
,
m
[
2
]);
}
else
{
setArg
(
m
[
1
],
m
[
2
]);
}
}
}
else
if
(
arg
.
match
(
negatedBoolean
)
&&
configuration
[
'boolean-negation'
])
{
m
=
arg
.
match
(
negatedBoolean
);
if
(
m
!==
null
&&
Array
.
isArray
(
m
)
&&
m
.
length
>=
2
)
{
key
=
m
[
1
];
setArg
(
key
,
checkAllAliases
(
key
,
flags
.
arrays
)
?
[
false
]
:
false
);
}
// -- separated by space.
}
else
if
(
arg
.
match
(
/^--.+/
)
||
(
!
configuration
[
'short-option-groups'
]
&&
arg
.
match
(
/^-[^-]+/
)))
{
m
=
arg
.
match
(
/^--?(.+)/
);
if
(
m
!==
null
&&
Array
.
isArray
(
m
)
&&
m
.
length
>=
2
)
{
key
=
m
[
1
];
if
(
checkAllAliases
(
key
,
flags
.
arrays
))
{
// array format = '--foo a b c'
i
=
eatArray
(
i
,
key
,
args
);
}
else
if
(
checkAllAliases
(
key
,
flags
.
nargs
)
!==
false
)
{
// nargs format = '--foo a b c'
// should be truthy even if: flags.nargs[key] === 0
i
=
eatNargs
(
i
,
key
,
args
);
}
else
{
next
=
args
[
i
+
1
];
if
(
next
!==
undefined
&&
(
!
next
.
match
(
/^-/
)
||
next
.
match
(
negative
))
&&
!
checkAllAliases
(
key
,
flags
.
bools
)
&&
!
checkAllAliases
(
key
,
flags
.
counts
))
{
setArg
(
key
,
next
);
i
++
;
}
else
if
(
/^(true|false)$/
.
test
(
next
))
{
setArg
(
key
,
next
);
i
++
;
}
else
{
setArg
(
key
,
defaultValue
(
key
));
}
}
}
// dot-notation flag separated by '='.
}
else
if
(
arg
.
match
(
/^-.\..+=/
))
{
m
=
arg
.
match
(
/^-([^=]+)=([\s\S]*)$/
);
if
(
m
!==
null
&&
Array
.
isArray
(
m
)
&&
m
.
length
>=
3
)
{
setArg
(
m
[
1
],
m
[
2
]);
}
// dot-notation flag separated by space.
}
else
if
(
arg
.
match
(
/^-.\..+/
)
&&
!
arg
.
match
(
negative
))
{
next
=
args
[
i
+
1
];
m
=
arg
.
match
(
/^-(.\..+)/
);
if
(
m
!==
null
&&
Array
.
isArray
(
m
)
&&
m
.
length
>=
2
)
{
key
=
m
[
1
];
if
(
next
!==
undefined
&&
!
next
.
match
(
/^-/
)
&&
!
checkAllAliases
(
key
,
flags
.
bools
)
&&
!
checkAllAliases
(
key
,
flags
.
counts
))
{
setArg
(
key
,
next
);
i
++
;
}
else
{
setArg
(
key
,
defaultValue
(
key
));
}
}
}
else
if
(
arg
.
match
(
/^-[^-]+/
)
&&
!
arg
.
match
(
negative
))
{
letters
=
arg
.
slice
(
1
,
-
1
).
split
(
''
);
broken
=
false
;
for
(
let
j
=
0
;
j
<
letters
.
length
;
j
++
)
{
next
=
arg
.
slice
(
j
+
2
);
if
(
letters
[
j
+
1
]
&&
letters
[
j
+
1
]
===
'='
)
{
value
=
arg
.
slice
(
j
+
3
);
key
=
letters
[
j
];
if
(
checkAllAliases
(
key
,
flags
.
arrays
))
{
// array format = '-f=a b c'
i
=
eatArray
(
i
,
key
,
args
,
value
);
}
else
if
(
checkAllAliases
(
key
,
flags
.
nargs
)
!==
false
)
{
// nargs format = '-f=monkey washing cat'
i
=
eatNargs
(
i
,
key
,
args
,
value
);
}
else
{
setArg
(
key
,
value
);
}
broken
=
true
;
break
;
}
if
(
next
===
'-'
)
{
setArg
(
letters
[
j
],
next
);
continue
;
}
// current letter is an alphabetic character and next value is a number
if
(
/[A-Za-z]/
.
test
(
letters
[
j
])
&&
/^-?\d+(\.\d*)?(e-?\d+)?$/
.
test
(
next
)
&&
checkAllAliases
(
next
,
flags
.
bools
)
===
false
)
{
setArg
(
letters
[
j
],
next
);
broken
=
true
;
break
;
}
if
(
letters
[
j
+
1
]
&&
letters
[
j
+
1
].
match
(
/\W/
))
{
setArg
(
letters
[
j
],
next
);
broken
=
true
;
break
;
}
else
{
setArg
(
letters
[
j
],
defaultValue
(
letters
[
j
]));
}
}
key
=
arg
.
slice
(
-
1
)[
0
];
if
(
!
broken
&&
key
!==
'-'
)
{
if
(
checkAllAliases
(
key
,
flags
.
arrays
))
{
// array format = '-f a b c'
i
=
eatArray
(
i
,
key
,
args
);
}
else
if
(
checkAllAliases
(
key
,
flags
.
nargs
)
!==
false
)
{
// nargs format = '-f a b c'
// should be truthy even if: flags.nargs[key] === 0
i
=
eatNargs
(
i
,
key
,
args
);
}
else
{
next
=
args
[
i
+
1
];
if
(
next
!==
undefined
&&
(
!
/^(-|--)[^-]/
.
test
(
next
)
||
next
.
match
(
negative
))
&&
!
checkAllAliases
(
key
,
flags
.
bools
)
&&
!
checkAllAliases
(
key
,
flags
.
counts
))
{
setArg
(
key
,
next
);
i
++
;
}
else
if
(
/^(true|false)$/
.
test
(
next
))
{
setArg
(
key
,
next
);
i
++
;
}
else
{
setArg
(
key
,
defaultValue
(
key
));
}
}
}
}
else
if
(
arg
.
match
(
/^-[0-9]$/
)
&&
arg
.
match
(
negative
)
&&
checkAllAliases
(
arg
.
slice
(
1
),
flags
.
bools
))
{
// single-digit boolean alias, e.g: xargs -0
key
=
arg
.
slice
(
1
);
setArg
(
key
,
defaultValue
(
key
));
}
else
if
(
arg
===
'--'
)
{
notFlags
=
args
.
slice
(
i
+
1
);
break
;
}
else
if
(
configuration
[
'halt-at-non-option'
])
{
notFlags
=
args
.
slice
(
i
);
break
;
}
else
{
pushPositional
(
arg
);
}
}
// order of precedence:
// 1. command line arg
// 2. value from env var
// 3. value from config file
// 4. value from config objects
// 5. configured default value
applyEnvVars
(
argv
,
true
);
// special case: check env vars that point to config file
applyEnvVars
(
argv
,
false
);
setConfig
(
argv
);
setConfigObjects
();
applyDefaultsAndAliases
(
argv
,
flags
.
aliases
,
defaults
,
true
);
applyCoercions
(
argv
);
if
(
configuration
[
'set-placeholder-key'
])
setPlaceholderKeys
(
argv
);
// for any counts either not in args or without an explicit default, set to 0
Object
.
keys
(
flags
.
counts
).
forEach
(
function
(
key
)
{
if
(
!
hasKey
(
argv
,
key
.
split
(
'.'
)))
setArg
(
key
,
0
);
});
// '--' defaults to undefined.
if
(
notFlagsOption
&&
notFlags
.
length
)
argv
[
notFlagsArgv
]
=
[];
notFlags
.
forEach
(
function
(
key
)
{
argv
[
notFlagsArgv
].
push
(
key
);
});
if
(
configuration
[
'camel-case-expansion'
]
&&
configuration
[
'strip-dashed'
])
{
Object
.
keys
(
argv
).
filter
(
key
=>
key
!==
'--'
&&
key
.
includes
(
'-'
)).
forEach
(
key
=>
{
delete
argv
[
key
];
});
}
if
(
configuration
[
'strip-aliased'
])
{
;
[].
concat
(...
Object
.
keys
(
aliases
).
map
(
k
=>
aliases
[
k
])).
forEach
(
alias
=>
{
if
(
configuration
[
'camel-case-expansion'
]
&&
alias
.
includes
(
'-'
))
{
delete
argv
[
alias
.
split
(
'.'
).
map
(
prop
=>
camelCase
(
prop
)).
join
(
'.'
)];
}
delete
argv
[
alias
];
});
}
// Push argument into positional array, applying numeric coercion:
function
pushPositional
(
arg
)
{
const
maybeCoercedNumber
=
maybeCoerceNumber
(
'_'
,
arg
);
if
(
typeof
maybeCoercedNumber
===
'string'
||
typeof
maybeCoercedNumber
===
'number'
)
{
argv
.
_
.
push
(
maybeCoercedNumber
);
}
}
// how many arguments should we consume, based
// on the nargs option?
function
eatNargs
(
i
,
key
,
args
,
argAfterEqualSign
)
{
let
ii
;
let
toEat
=
checkAllAliases
(
key
,
flags
.
nargs
);
// NaN has a special meaning for the array type, indicating that one or
// more values are expected.
toEat
=
typeof
toEat
!==
'number'
||
isNaN
(
toEat
)
?
1
:
toEat
;
if
(
toEat
===
0
)
{
if
(
!
isUndefined
(
argAfterEqualSign
))
{
error
=
Error
(
__
(
'Argument unexpected for: %s'
,
key
));
}
setArg
(
key
,
defaultValue
(
key
));
return
i
;
}
let
available
=
isUndefined
(
argAfterEqualSign
)
?
0
:
1
;
if
(
configuration
[
'nargs-eats-options'
])
{
// classic behavior, yargs eats positional and dash arguments.
if
(
args
.
length
-
(
i
+
1
)
+
available
<
toEat
)
{
error
=
Error
(
__
(
'Not enough arguments following: %s'
,
key
));
}
available
=
toEat
;
}
else
{
// nargs will not consume flag arguments, e.g., -abc, --foo,
// and terminates when one is observed.
for
(
ii
=
i
+
1
;
ii
<
args
.
length
;
ii
++
)
{
if
(
!
args
[
ii
].
match
(
/^-[^0-9]/
)
||
args
[
ii
].
match
(
negative
)
||
isUnknownOptionAsArg
(
args
[
ii
]))
available
++
;
else
break
;
}
if
(
available
<
toEat
)
error
=
Error
(
__
(
'Not enough arguments following: %s'
,
key
));
}
let
consumed
=
Math
.
min
(
available
,
toEat
);
if
(
!
isUndefined
(
argAfterEqualSign
)
&&
consumed
>
0
)
{
setArg
(
key
,
argAfterEqualSign
);
consumed
--
;
}
for
(
ii
=
i
+
1
;
ii
<
(
consumed
+
i
+
1
);
ii
++
)
{
setArg
(
key
,
args
[
ii
]);
}
return
(
i
+
consumed
);
}
// if an option is an array, eat all non-hyphenated arguments
// following it... YUM!
// e.g., --foo apple banana cat becomes ["apple", "banana", "cat"]
function
eatArray
(
i
,
key
,
args
,
argAfterEqualSign
)
{
let
argsToSet
=
[];
let
next
=
argAfterEqualSign
||
args
[
i
+
1
];
// If both array and nargs are configured, enforce the nargs count:
const
nargsCount
=
checkAllAliases
(
key
,
flags
.
nargs
);
if
(
checkAllAliases
(
key
,
flags
.
bools
)
&&
!
(
/^(true|false)$/
.
test
(
next
)))
{
argsToSet
.
push
(
true
);
}
else
if
(
isUndefined
(
next
)
||
(
isUndefined
(
argAfterEqualSign
)
&&
/^-/
.
test
(
next
)
&&
!
negative
.
test
(
next
)
&&
!
isUnknownOptionAsArg
(
next
)))
{
// for keys without value ==> argsToSet remains an empty []
// set user default value, if available
if
(
defaults
[
key
]
!==
undefined
)
{
const
defVal
=
defaults
[
key
];
argsToSet
=
Array
.
isArray
(
defVal
)
?
defVal
:
[
defVal
];
}
}
else
{
// value in --option=value is eaten as is
if
(
!
isUndefined
(
argAfterEqualSign
))
{
argsToSet
.
push
(
processValue
(
key
,
argAfterEqualSign
));
}
for
(
let
ii
=
i
+
1
;
ii
<
args
.
length
;
ii
++
)
{
if
((
!
configuration
[
'greedy-arrays'
]
&&
argsToSet
.
length
>
0
)
||
(
nargsCount
&&
typeof
nargsCount
===
'number'
&&
argsToSet
.
length
>=
nargsCount
))
break
;
next
=
args
[
ii
];
if
(
/^-/
.
test
(
next
)
&&
!
negative
.
test
(
next
)
&&
!
isUnknownOptionAsArg
(
next
))
break
;
i
=
ii
;
argsToSet
.
push
(
processValue
(
key
,
next
));
}
}
// If both array and nargs are configured, create an error if less than
// nargs positionals were found. NaN has special meaning, indicating
// that at least one value is required (more are okay).
if
(
typeof
nargsCount
===
'number'
&&
((
nargsCount
&&
argsToSet
.
length
<
nargsCount
)
||
(
isNaN
(
nargsCount
)
&&
argsToSet
.
length
===
0
)))
{
error
=
Error
(
__
(
'Not enough arguments following: %s'
,
key
));
}
setArg
(
key
,
argsToSet
);
return
i
;
}
function
setArg
(
key
,
val
)
{
if
(
/-/
.
test
(
key
)
&&
configuration
[
'camel-case-expansion'
])
{
const
alias
=
key
.
split
(
'.'
).
map
(
function
(
prop
)
{
return
camelCase
(
prop
);
}).
join
(
'.'
);
addNewAlias
(
key
,
alias
);
}
const
value
=
processValue
(
key
,
val
);
const
splitKey
=
key
.
split
(
'.'
);
setKey
(
argv
,
splitKey
,
value
);
// handle populating aliases of the full key
if
(
flags
.
aliases
[
key
])
{
flags
.
aliases
[
key
].
forEach
(
function
(
x
)
{
const
keyProperties
=
x
.
split
(
'.'
);
setKey
(
argv
,
keyProperties
,
value
);
});
}
// handle populating aliases of the first element of the dot-notation key
if
(
splitKey
.
length
>
1
&&
configuration
[
'dot-notation'
])
{
;
(
flags
.
aliases
[
splitKey
[
0
]]
||
[]).
forEach
(
function
(
x
)
{
let
keyProperties
=
x
.
split
(
'.'
);
// expand alias with nested objects in key
const
a
=
[].
concat
(
splitKey
);
a
.
shift
();
// nuke the old key.
keyProperties
=
keyProperties
.
concat
(
a
);
// populate alias only if is not already an alias of the full key
// (already populated above)
if
(
!
(
flags
.
aliases
[
key
]
||
[]).
includes
(
keyProperties
.
join
(
'.'
)))
{
setKey
(
argv
,
keyProperties
,
value
);
}
});
}
// Set normalize getter and setter when key is in 'normalize' but isn't an array
if
(
checkAllAliases
(
key
,
flags
.
normalize
)
&&
!
checkAllAliases
(
key
,
flags
.
arrays
))
{
const
keys
=
[
key
].
concat
(
flags
.
aliases
[
key
]
||
[]);
keys
.
forEach
(
function
(
key
)
{
Object
.
defineProperty
(
argvReturn
,
key
,
{
enumerable
:
true
,
get
()
{
return
val
;
},
set
(
value
)
{
val
=
typeof
value
===
'string'
?
mixin
.
normalize
(
value
)
:
value
;
}
});
});
}
}
function
addNewAlias
(
key
,
alias
)
{
if
(
!
(
flags
.
aliases
[
key
]
&&
flags
.
aliases
[
key
].
length
))
{
flags
.
aliases
[
key
]
=
[
alias
];
newAliases
[
alias
]
=
true
;
}
if
(
!
(
flags
.
aliases
[
alias
]
&&
flags
.
aliases
[
alias
].
length
))
{
addNewAlias
(
alias
,
key
);
}
}
function
processValue
(
key
,
val
)
{
// strings may be quoted, clean this up as we assign values.
if
(
typeof
val
===
'string'
&&
(
val
[
0
]
===
"'"
||
val
[
0
]
===
'"'
)
&&
val
[
val
.
length
-
1
]
===
val
[
0
])
{
val
=
val
.
substring
(
1
,
val
.
length
-
1
);
}
// handle parsing boolean arguments --foo=true --bar false.
if
(
checkAllAliases
(
key
,
flags
.
bools
)
||
checkAllAliases
(
key
,
flags
.
counts
))
{
if
(
typeof
val
===
'string'
)
val
=
val
===
'true'
;
}
let
value
=
Array
.
isArray
(
val
)
?
val
.
map
(
function
(
v
)
{
return
maybeCoerceNumber
(
key
,
v
);
})
:
maybeCoerceNumber
(
key
,
val
);
// increment a count given as arg (either no value or value parsed as boolean)
if
(
checkAllAliases
(
key
,
flags
.
counts
)
&&
(
isUndefined
(
value
)
||
typeof
value
===
'boolean'
))
{
value
=
increment
();
}
// Set normalized value when key is in 'normalize' and in 'arrays'
if
(
checkAllAliases
(
key
,
flags
.
normalize
)
&&
checkAllAliases
(
key
,
flags
.
arrays
))
{
if
(
Array
.
isArray
(
val
))
value
=
val
.
map
((
val
)
=>
{
return
mixin
.
normalize
(
val
);
});
else
value
=
mixin
.
normalize
(
val
);
}
return
value
;
}
function
maybeCoerceNumber
(
key
,
value
)
{
if
(
!
configuration
[
'parse-positional-numbers'
]
&&
key
===
'_'
)
return
value
;
if
(
!
checkAllAliases
(
key
,
flags
.
strings
)
&&
!
checkAllAliases
(
key
,
flags
.
bools
)
&&
!
Array
.
isArray
(
value
))
{
const
shouldCoerceNumber
=
looksLikeNumber
(
value
)
&&
configuration
[
'parse-numbers'
]
&&
(
Number
.
isSafeInteger
(
Math
.
floor
(
parseFloat
(
`
$
{
value
}
`
))));
if
(
shouldCoerceNumber
||
(
!
isUndefined
(
value
)
&&
checkAllAliases
(
key
,
flags
.
numbers
)))
{
value
=
Number
(
value
);
}
}
return
value
;
}
// set args from config.json file, this should be
// applied last so that defaults can be applied.
function
setConfig
(
argv
)
{
const
configLookup
=
Object
.
create
(
null
);
// expand defaults/aliases, in-case any happen to reference
// the config.json file.
applyDefaultsAndAliases
(
configLookup
,
flags
.
aliases
,
defaults
);
Object
.
keys
(
flags
.
configs
).
forEach
(
function
(
configKey
)
{
const
configPath
=
argv
[
configKey
]
||
configLookup
[
configKey
];
if
(
configPath
)
{
try
{
let
config
=
null
;
const
resolvedConfigPath
=
mixin
.
resolve
(
mixin
.
cwd
(),
configPath
);
const
resolveConfig
=
flags
.
configs
[
configKey
];
if
(
typeof
resolveConfig
===
'function'
)
{
try
{
config
=
resolveConfig
(
resolvedConfigPath
);
}
catch
(
e
)
{
config
=
e
;
}
if
(
config
instanceof
Error
)
{
error
=
config
;
return
;
}
}
else
{
config
=
mixin
.
require
(
resolvedConfigPath
);
}
setConfigObject
(
config
);
}
catch
(
ex
)
{
// Deno will receive a PermissionDenied error if an attempt is
// made to load config without the --allow-read flag:
if
(
ex
.
name
===
'PermissionDenied'
)
error
=
ex
;
else
if
(
argv
[
configKey
])
error
=
Error
(
__
(
'Invalid JSON config file: %s'
,
configPath
));
}
}
});
}
// set args from config object.
// it recursively checks nested objects.
function
setConfigObject
(
config
,
prev
)
{
Object
.
keys
(
config
).
forEach
(
function
(
key
)
{
const
value
=
config
[
key
];
const
fullKey
=
prev
?
prev
+
'.'
+
key
:
key
;
// if the value is an inner object and we have dot-notation
// enabled, treat inner objects in config the same as
// heavily nested dot notations (foo.bar.apple).
if
(
typeof
value
===
'object'
&&
value
!==
null
&&
!
Array
.
isArray
(
value
)
&&
configuration
[
'dot-notation'
])
{
// if the value is an object but not an array, check nested object
setConfigObject
(
value
,
fullKey
);
}
else
{
// setting arguments via CLI takes precedence over
// values within the config file.
if
(
!
hasKey
(
argv
,
fullKey
.
split
(
'.'
))
||
(
checkAllAliases
(
fullKey
,
flags
.
arrays
)
&&
configuration
[
'combine-arrays'
]))
{
setArg
(
fullKey
,
value
);
}
}
});
}
// set all config objects passed in opts
function
setConfigObjects
()
{
if
(
typeof
configObjects
!==
'undefined'
)
{
configObjects
.
forEach
(
function
(
configObject
)
{
setConfigObject
(
configObject
);
});
}
}
function
applyEnvVars
(
argv
,
configOnly
)
{
if
(
typeof
envPrefix
===
'undefined'
)
return
;
const
prefix
=
typeof
envPrefix
===
'string'
?
envPrefix
:
''
;
const
env
=
mixin
.
env
();
Object
.
keys
(
env
).
forEach
(
function
(
envVar
)
{
if
(
prefix
===
''
||
envVar
.
lastIndexOf
(
prefix
,
0
)
===
0
)
{
// get array of nested keys and convert them to camel case
const
keys
=
envVar
.
split
(
'__'
).
map
(
function
(
key
,
i
)
{
if
(
i
===
0
)
{
key
=
key
.
substring
(
prefix
.
length
);
}
return
camelCase
(
key
);
});
if
(((
configOnly
&&
flags
.
configs
[
keys
.
join
(
'.'
)])
||
!
configOnly
)
&&
!
hasKey
(
argv
,
keys
))
{
setArg
(
keys
.
join
(
'.'
),
env
[
envVar
]);
}
}
});
}
function
applyCoercions
(
argv
)
{
let
coerce
;
const
applied
=
new
Set
();
Object
.
keys
(
argv
).
forEach
(
function
(
key
)
{
if
(
!
applied
.
has
(
key
))
{
// If we haven't already coerced this option via one of its aliases
coerce
=
checkAllAliases
(
key
,
flags
.
coercions
);
if
(
typeof
coerce
===
'function'
)
{
try
{
const
value
=
maybeCoerceNumber
(
key
,
coerce
(
argv
[
key
]));
([].
concat
(
flags
.
aliases
[
key
]
||
[],
key
)).
forEach
(
ali
=>
{
applied
.
add
(
ali
);
argv
[
ali
]
=
value
;
});
}
catch
(
err
)
{
error
=
err
;
}
}
}
});
}
function
setPlaceholderKeys
(
argv
)
{
flags
.
keys
.
forEach
((
key
)
=>
{
// don't set placeholder keys for dot notation options 'foo.bar'.
if
(
~
key
.
indexOf
(
'.'
))
return
;
if
(
typeof
argv
[
key
]
===
'undefined'
)
argv
[
key
]
=
undefined
;
});
return
argv
;
}
function
applyDefaultsAndAliases
(
obj
,
aliases
,
defaults
,
canLog
=
false
)
{
Object
.
keys
(
defaults
).
forEach
(
function
(
key
)
{
if
(
!
hasKey
(
obj
,
key
.
split
(
'.'
)))
{
setKey
(
obj
,
key
.
split
(
'.'
),
defaults
[
key
]);
if
(
canLog
)
defaulted
[
key
]
=
true
;
(
aliases
[
key
]
||
[]).
forEach
(
function
(
x
)
{
if
(
hasKey
(
obj
,
x
.
split
(
'.'
)))
return
;
setKey
(
obj
,
x
.
split
(
'.'
),
defaults
[
key
]);
});
}
});
}
function
hasKey
(
obj
,
keys
)
{
let
o
=
obj
;
if
(
!
configuration
[
'dot-notation'
])
keys
=
[
keys
.
join
(
'.'
)];
keys
.
slice
(
0
,
-
1
).
forEach
(
function
(
key
)
{
o
=
(
o
[
key
]
||
{});
});
const
key
=
keys
[
keys
.
length
-
1
];
if
(
typeof
o
!==
'object'
)
return
false
;
else
return
key
in
o
;
}
function
setKey
(
obj
,
keys
,
value
)
{
let
o
=
obj
;
if
(
!
configuration
[
'dot-notation'
])
keys
=
[
keys
.
join
(
'.'
)];
keys
.
slice
(
0
,
-
1
).
forEach
(
function
(
key
)
{
// TODO(bcoe): in the next major version of yargs, switch to
// Object.create(null) for dot notation:
key
=
sanitizeKey
(
key
);
if
(
typeof
o
===
'object'
&&
o
[
key
]
===
undefined
)
{
o
[
key
]
=
{};
}
if
(
typeof
o
[
key
]
!==
'object'
||
Array
.
isArray
(
o
[
key
]))
{
// ensure that o[key] is an array, and that the last item is an empty object.
if
(
Array
.
isArray
(
o
[
key
]))
{
o
[
key
].
push
({});
}
else
{
o
[
key
]
=
[
o
[
key
],
{}];
}
// we want to update the empty object at the end of the o[key] array, so set o to that object
o
=
o
[
key
][
o
[
key
].
length
-
1
];
}
else
{
o
=
o
[
key
];
}
});
// TODO(bcoe): in the next major version of yargs, switch to
// Object.create(null) for dot notation:
const
key
=
sanitizeKey
(
keys
[
keys
.
length
-
1
]);
const
isTypeArray
=
checkAllAliases
(
keys
.
join
(
'.'
),
flags
.
arrays
);
const
isValueArray
=
Array
.
isArray
(
value
);
let
duplicate
=
configuration
[
'duplicate-arguments-array'
];
// nargs has higher priority than duplicate
if
(
!
duplicate
&&
checkAllAliases
(
key
,
flags
.
nargs
))
{
duplicate
=
true
;
if
((
!
isUndefined
(
o
[
key
])
&&
flags
.
nargs
[
key
]
===
1
)
||
(
Array
.
isArray
(
o
[
key
])
&&
o
[
key
].
length
===
flags
.
nargs
[
key
]))
{
o
[
key
]
=
undefined
;
}
}
if
(
value
===
increment
())
{
o
[
key
]
=
increment
(
o
[
key
]);
}
else
if
(
Array
.
isArray
(
o
[
key
]))
{
if
(
duplicate
&&
isTypeArray
&&
isValueArray
)
{
o
[
key
]
=
configuration
[
'flatten-duplicate-arrays'
]
?
o
[
key
].
concat
(
value
)
:
(
Array
.
isArray
(
o
[
key
][
0
])
?
o
[
key
]
:
[
o
[
key
]]).
concat
([
value
]);
}
else
if
(
!
duplicate
&&
Boolean
(
isTypeArray
)
===
Boolean
(
isValueArray
))
{
o
[
key
]
=
value
;
}
else
{
o
[
key
]
=
o
[
key
].
concat
([
value
]);
}
}
else
if
(
o
[
key
]
===
undefined
&&
isTypeArray
)
{
o
[
key
]
=
isValueArray
?
value
:
[
value
];
}
else
if
(
duplicate
&&
!
(
o
[
key
]
===
undefined
||
checkAllAliases
(
key
,
flags
.
counts
)
||
checkAllAliases
(
key
,
flags
.
bools
)))
{
o
[
key
]
=
[
o
[
key
],
value
];
}
else
{
o
[
key
]
=
value
;
}
}
// extend the aliases list with inferred aliases.
function
extendAliases
(...
args
)
{
args
.
forEach
(
function
(
obj
)
{
Object
.
keys
(
obj
||
{}).
forEach
(
function
(
key
)
{
// short-circuit if we've already added a key
// to the aliases array, for example it might
// exist in both 'opts.default' and 'opts.key'.
if
(
flags
.
aliases
[
key
])
return
;
flags
.
aliases
[
key
]
=
[].
concat
(
aliases
[
key
]
||
[]);
// For "--option-name", also set argv.optionName
flags
.
aliases
[
key
].
concat
(
key
).
forEach
(
function
(
x
)
{
if
(
/-/
.
test
(
x
)
&&
configuration
[
'camel-case-expansion'
])
{
const
c
=
camelCase
(
x
);
if
(
c
!==
key
&&
flags
.
aliases
[
key
].
indexOf
(
c
)
===
-
1
)
{
flags
.
aliases
[
key
].
push
(
c
);
newAliases
[
c
]
=
true
;
}
}
});
// For "--optionName", also set argv['option-name']
flags
.
aliases
[
key
].
concat
(
key
).
forEach
(
function
(
x
)
{
if
(
x
.
length
>
1
&&
/[A-Z]/
.
test
(
x
)
&&
configuration
[
'camel-case-expansion'
])
{
const
c
=
decamelize
(
x
,
'-'
);
if
(
c
!==
key
&&
flags
.
aliases
[
key
].
indexOf
(
c
)
===
-
1
)
{
flags
.
aliases
[
key
].
push
(
c
);
newAliases
[
c
]
=
true
;
}
}
});
flags
.
aliases
[
key
].
forEach
(
function
(
x
)
{
flags
.
aliases
[
x
]
=
[
key
].
concat
(
flags
.
aliases
[
key
].
filter
(
function
(
y
)
{
return
x
!==
y
;
}));
});
});
});
}
function
checkAllAliases
(
key
,
flag
)
{
const
toCheck
=
[].
concat
(
flags
.
aliases
[
key
]
||
[],
key
);
const
keys
=
Object
.
keys
(
flag
);
const
setAlias
=
toCheck
.
find
(
key
=>
keys
.
includes
(
key
));
return
setAlias
?
flag
[
setAlias
]
:
false
;
}
function
hasAnyFlag
(
key
)
{
const
flagsKeys
=
Object
.
keys
(
flags
);
const
toCheck
=
[].
concat
(
flagsKeys
.
map
(
k
=>
flags
[
k
]));
return
toCheck
.
some
(
function
(
flag
)
{
return
Array
.
isArray
(
flag
)
?
flag
.
includes
(
key
)
:
flag
[
key
];
});
}
function
hasFlagsMatching
(
arg
,
...
patterns
)
{
const
toCheck
=
[].
concat
(...
patterns
);
return
toCheck
.
some
(
function
(
pattern
)
{
const
match
=
arg
.
match
(
pattern
);
return
match
&&
hasAnyFlag
(
match
[
1
]);
});
}
// based on a simplified version of the short flag group parsing logic
function
hasAllShortFlags
(
arg
)
{
// if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group
if
(
arg
.
match
(
negative
)
||
!
arg
.
match
(
/^-[^-]+/
))
{
return
false
;
}
let
hasAllFlags
=
true
;
let
next
;
const
letters
=
arg
.
slice
(
1
).
split
(
''
);
for
(
let
j
=
0
;
j
<
letters
.
length
;
j
++
)
{
next
=
arg
.
slice
(
j
+
2
);
if
(
!
hasAnyFlag
(
letters
[
j
]))
{
hasAllFlags
=
false
;
break
;
}
if
((
letters
[
j
+
1
]
&&
letters
[
j
+
1
]
===
'='
)
||
next
===
'-'
||
(
/[A-Za-z]/
.
test
(
letters
[
j
])
&&
/^-?\d+(\.\d*)?(e-?\d+)?$/
.
test
(
next
))
||
(
letters
[
j
+
1
]
&&
letters
[
j
+
1
].
match
(
/\W/
)))
{
break
;
}
}
return
hasAllFlags
;
}
function
isUnknownOptionAsArg
(
arg
)
{
return
configuration
[
'unknown-options-as-args'
]
&&
isUnknownOption
(
arg
);
}
function
isUnknownOption
(
arg
)
{
arg
=
arg
.
replace
(
/^-{3,}/
,
'--'
);
// ignore negative numbers
if
(
arg
.
match
(
negative
))
{
return
false
;
}
// if this is a short option group and all of them are configured, it isn't unknown
if
(
hasAllShortFlags
(
arg
))
{
return
false
;
}
// e.g. '--count=2'
const
flagWithEquals
=
/^-+([^=]+?)=[\s\S]*$/
;
// e.g. '-a' or '--arg'
const
normalFlag
=
/^-+([^=]+?)$/
;
// e.g. '-a-'
const
flagEndingInHyphen
=
/^-+([^=]+?)-$/
;
// e.g. '-abc123'
const
flagEndingInDigits
=
/^-+([^=]+?\d+)$/
;
// e.g. '-a/usr/local'
const
flagEndingInNonWordCharacters
=
/^-+([^=]+?)\W+.*$/
;
// check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method
return
!
hasFlagsMatching
(
arg
,
flagWithEquals
,
negatedBoolean
,
normalFlag
,
flagEndingInHyphen
,
flagEndingInDigits
,
flagEndingInNonWordCharacters
);
}
// make a best effort to pick a default value
// for an option based on name and type.
function
defaultValue
(
key
)
{
if
(
!
checkAllAliases
(
key
,
flags
.
bools
)
&&
!
checkAllAliases
(
key
,
flags
.
counts
)
&&
`
$
{
key
}
`
in
defaults
)
{
return
defaults
[
key
];
}
else
{
return
defaultForType
(
guessType
(
key
));
}
}
// return a default value, given the type of a flag.,
function
defaultForType
(
type
)
{
const
def
=
{
[
DefaultValuesForTypeKey
.
BOOLEAN
]
:
true
,
[
DefaultValuesForTypeKey
.
STRING
]
:
''
,
[
DefaultValuesForTypeKey
.
NUMBER
]
:
undefined
,
[
DefaultValuesForTypeKey
.
ARRAY
]
:
[]
};
return
def
[
type
];
}
// given a flag, enforce a default type.
function
guessType
(
key
)
{
let
type
=
DefaultValuesForTypeKey
.
BOOLEAN
;
if
(
checkAllAliases
(
key
,
flags
.
strings
))
type
=
DefaultValuesForTypeKey
.
STRING
;
else
if
(
checkAllAliases
(
key
,
flags
.
numbers
))
type
=
DefaultValuesForTypeKey
.
NUMBER
;
else
if
(
checkAllAliases
(
key
,
flags
.
bools
))
type
=
DefaultValuesForTypeKey
.
BOOLEAN
;
else
if
(
checkAllAliases
(
key
,
flags
.
arrays
))
type
=
DefaultValuesForTypeKey
.
ARRAY
;
return
type
;
}
function
isUndefined
(
num
)
{
return
num
===
undefined
;
}
// check user configuration settings for inconsistencies
function
checkConfiguration
()
{
// count keys should not be set as array/narg
Object
.
keys
(
flags
.
counts
).
find
(
key
=>
{
if
(
checkAllAliases
(
key
,
flags
.
arrays
))
{
error
=
Error
(
__
(
'Invalid configuration: %s, opts.count excludes opts.array.'
,
key
));
return
true
;
}
else
if
(
checkAllAliases
(
key
,
flags
.
nargs
))
{
error
=
Error
(
__
(
'Invalid configuration: %s, opts.count excludes opts.narg.'
,
key
));
return
true
;
}
return
false
;
});
}
return
{
aliases
:
Object
.
assign
({},
flags
.
aliases
),
argv
:
Object
.
assign
(
argvReturn
,
argv
),
configuration
:
configuration
,
defaulted
:
Object
.
assign
({},
defaulted
),
error
:
error
,
newAliases
:
Object
.
assign
({},
newAliases
)
};
}
}
// if any aliases reference each other, we should
// merge them together.
function
combineAliases
(
aliases
)
{
const
aliasArrays
=
[];
const
combined
=
Object
.
create
(
null
);
let
change
=
true
;
// turn alias lookup hash {key: ['alias1', 'alias2']} into
// a simple array ['key', 'alias1', 'alias2']
Object
.
keys
(
aliases
).
forEach
(
function
(
key
)
{
aliasArrays
.
push
([].
concat
(
aliases
[
key
],
key
));
});
// combine arrays until zero changes are
// made in an iteration.
while
(
change
)
{
change
=
false
;
for
(
let
i
=
0
;
i
<
aliasArrays
.
length
;
i
++
)
{
for
(
let
ii
=
i
+
1
;
ii
<
aliasArrays
.
length
;
ii
++
)
{
const
intersect
=
aliasArrays
[
i
].
filter
(
function
(
v
)
{
return
aliasArrays
[
ii
].
indexOf
(
v
)
!==
-
1
;
});
if
(
intersect
.
length
)
{
aliasArrays
[
i
]
=
aliasArrays
[
i
].
concat
(
aliasArrays
[
ii
]);
aliasArrays
.
splice
(
ii
,
1
);
change
=
true
;
break
;
}
}
}
}
// map arrays back to the hash-lookup (de-dupe while
// we're at it).
aliasArrays
.
forEach
(
function
(
aliasArray
)
{
aliasArray
=
aliasArray
.
filter
(
function
(
v
,
i
,
self
)
{
return
self
.
indexOf
(
v
)
===
i
;
});
const
lastAlias
=
aliasArray
.
pop
();
if
(
lastAlias
!==
undefined
&&
typeof
lastAlias
===
'string'
)
{
combined
[
lastAlias
]
=
aliasArray
;
}
});
return
combined
;
}
// this function should only be called when a count is given as an arg
// it is NOT called to set a default value
// thus we can start the count at 1 instead of 0
function
increment
(
orig
)
{
return
orig
!==
undefined
?
orig
+
1
:
1
;
}
// TODO(bcoe): in the next major version of yargs, switch to
// Object.create(null) for dot notation:
function
sanitizeKey
(
key
)
{
if
(
key
===
'__proto__'
)
return
'___proto___'
;
return
key
;
}
Event Timeline
Log In to Comment