feat: add configuration option to "collect-unknown-options" (#181) · yargs/yargs-parser@7909cc4
@@ -26,7 +26,8 @@ function parse (args, opts) {
2626'set-placeholder-key': false,
2727'halt-at-non-option': false,
2828'strip-aliased': false,
29-'strip-dashed': false
29+'strip-dashed': false,
30+'collect-unknown-options': false
3031}, opts.configuration)
3132var defaults = opts.default || {}
3233var configObjects = opts.configObjects || []
@@ -142,8 +143,10 @@ function parse (args, opts) {
142143var next
143144var value
144145146+if (configuration['collect-unknown-options'] && isUnknownOption(arg)) {
147+argv._.push(arg)
145148// -- separated by =
146-if (arg.match(/^--.+=/) || (
149+} else if (arg.match(/^--.+=/) || (
147150!configuration['short-option-groups'] && arg.match(/^-.+=/)
148151)) {
149152// Using [\s\S] instead of . because js doesn't support the
@@ -757,6 +760,74 @@ function parse (args, opts) {
757760return isSet
758761}
759762763+function hasAnyFlag (key) {
764+var isSet = false
765+// XXX Switch to [].concat(...Object.values(flags)) once node.js 6 is dropped
766+var toCheck = [].concat(...Object.keys(flags).map(k => flags[k]))
767+768+toCheck.forEach(function (flag) {
769+if (flag[key]) isSet = flag[key]
770+})
771+772+return isSet
773+}
774+775+function hasFlagsMatching (arg, ...patterns) {
776+var hasFlag = false
777+var toCheck = [].concat(...patterns)
778+toCheck.forEach(function (pattern) {
779+var match = arg.match(pattern)
780+if (match && hasAnyFlag(match[1])) {
781+hasFlag = true
782+}
783+})
784+return hasFlag
785+}
786+787+// based on a simplified version of the short flag group parsing logic
788+function hasAllShortFlags (arg) {
789+// if this is a negative number, or doesn't start with a single hyphen, it's not a short flag group
790+if (arg.match(negative) || !arg.match(/^-[^-]+/)) { return false }
791+var hasAllFlags = true
792+var letters = arg.slice(1).split('')
793+var next
794+for (var j = 0; j < letters.length; j++) {
795+next = arg.slice(j + 2)
796+797+if (!hasAnyFlag(letters[j])) {
798+hasAllFlags = false
799+break
800+}
801+802+if ((letters[j + 1] && letters[j + 1] === '=') ||
803+next === '-' ||
804+(/[A-Za-z]/.test(letters[j]) && /^-?\d+(\.\d*)?(e-?\d+)?$/.test(next)) ||
805+(letters[j + 1] && letters[j + 1].match(/\W/))) {
806+break
807+}
808+}
809+return hasAllFlags
810+}
811+812+function isUnknownOption (arg) {
813+// ignore negative numbers
814+if (arg.match(negative)) { return false }
815+// if this is a short option group and all of them are configured, it isn't unknown
816+if (hasAllShortFlags(arg)) { return false }
817+// e.g. '--count=2'
818+const flagWithEquals = /^-+([^=]+?)=[\s\S]*$/
819+// e.g. '-a' or '--arg'
820+const normalFlag = /^-+([^=]+?)$/
821+// e.g. '-a-'
822+const flagEndingInHyphen = /^-+([^=]+?)-$/
823+// e.g. '-abc123'
824+const flagEndingInDigits = /^-+([^=]+?)\d+$/
825+// e.g. '-a/usr/local'
826+const flagEndingInNonWordCharacters = /^-+([^=]+?)\W+.*$/
827+// check the different types of flag styles, including negatedBoolean, a pattern defined near the start of the parse method
828+return !hasFlagsMatching(arg, flagWithEquals, negatedBoolean, normalFlag, flagEndingInHyphen, flagEndingInDigits, flagEndingInNonWordCharacters)
829+}
830+760831// make a best effor to pick a default value
761832// for an option based on name and type.
762833function defaultValue (key) {