Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add configuration option "collect-unknown-options" #181

Merged
merged 10 commits into from Sep 6, 2019
22 changes: 22 additions & 0 deletions README.md
Expand Up @@ -386,6 +386,28 @@ node example.js --test-field 1
{ _: [], testField: 1 }
```

### collect unknown options

* default: `false`
* key: `collect-unknown-options`

Should unknown options be collected into `_`? An unknown option is one that is not
configured in `opts`.

_If disabled_

```sh
node example.js --unknown-option --known-option 2
{ _: [], unknownOption: true, knownOption: 2 }
```

_If enabled_

```sh
node example.js --unknown-option --known-option 2
{ _: ['--unknown-option'], knownOption: 2 }
```

## Special Thanks

The yargs project evolves from optimist and minimist. It owes its
Expand Down
75 changes: 73 additions & 2 deletions index.js
Expand Up @@ -26,7 +26,8 @@ function parse (args, opts) {
'set-placeholder-key': false,
'halt-at-non-option': false,
'strip-aliased': false,
'strip-dashed': false
'strip-dashed': false,
'collect-unknown-options': false
}, opts.configuration)
var defaults = opts.default || {}
var configObjects = opts.configObjects || []
Expand Down Expand Up @@ -142,8 +143,10 @@ function parse (args, opts) {
var next
var value

if (configuration['collect-unknown-options'] && isUnknownOption(arg)) {
argv._.push(arg)
// -- separated by =
if (arg.match(/^--.+=/) || (
} else if (arg.match(/^--.+=/) || (
!configuration['short-option-groups'] && arg.match(/^-.+=/)
)) {
// Using [\s\S] instead of . because js doesn't support the
Expand Down Expand Up @@ -757,6 +760,74 @@ function parse (args, opts) {
return isSet
}

function hasAnyFlag (key) {
var isSet = false
// XXX Switch to [].concat(...Object.values(flags)) once node.js 6 is dropped
henderea marked this conversation as resolved.
Show resolved Hide resolved
var toCheck = [].concat(...Object.keys(flags).map(k => flags[k]))

toCheck.forEach(function (flag) {
if (flag[key]) isSet = flag[key]
})

return isSet
}

function hasFlagsMatching (arg, ...patterns) {
var hasFlag = false
var toCheck = [].concat(...patterns)
toCheck.forEach(function (pattern) {
var match = arg.match(pattern)
if (match && hasAnyFlag(match[1])) {
hasFlag = true
}
})
return hasFlag
}

// 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 }
var hasAllFlags = true
var letters = arg.slice(1).split('')
var next
for (var 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 isUnknownOption (arg) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this refactor makes me quite happy 😄

// 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 effor to pick a default value
// for an option based on name and type.
function defaultValue (key) {
Expand Down