From 7ede6c71ae545fe420532fe89d7fdc502a41ea89 Mon Sep 17 00:00:00 2001 From: Luke Karrys Date: Sun, 28 May 2023 23:52:50 -0700 Subject: [PATCH] feat: allow setting `typeDefault` on `nopt` and `clean` lib methods Setting this option allows bypassing the default handling of unknown keys when cleaning data. --- lib/nopt-lib.js | 34 +++++++++++++++++++++---------- lib/nopt.js | 3 +-- package.json | 6 +++--- test/type-default.js | 48 ++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 75 insertions(+), 16 deletions(-) create mode 100644 test/type-default.js diff --git a/lib/nopt-lib.js b/lib/nopt-lib.js index 89d269f..f4c2240 100644 --- a/lib/nopt-lib.js +++ b/lib/nopt-lib.js @@ -2,7 +2,7 @@ var abbrev = require('abbrev') const debug = require('./debug') const defaultTypeDefs = require('./type-defs') -function nopt (args, { types, shorthands, typeDefs, invalidHandler }) { +function nopt (args, { types, shorthands, typeDefs, invalidHandler, typeDefault }) { debug(types, shorthands, args, typeDefs) var data = {} @@ -15,7 +15,7 @@ function nopt (args, { types, shorthands, typeDefs, invalidHandler }) { parse(args, data, argv.remain, { typeDefs, types, shorthands }) // now data is full - clean(data, { types, typeDefs, invalidHandler }) + clean(data, { types, typeDefs, invalidHandler, typeDefault }) data.argv = argv Object.defineProperty(data.argv, 'toString', { @@ -28,15 +28,19 @@ function nopt (args, { types, shorthands, typeDefs, invalidHandler }) { return data } -function clean (data, { types, typeDefs, invalidHandler }) { +function clean (data, { types, typeDefs, invalidHandler, typeDefault }) { const StringType = typeDefs.String.type const NumberType = typeDefs.Number.type const ArrayType = typeDefs.Array.type const BooleanType = typeDefs.Boolean.type const DateType = typeDefs.Date.type + const hasTypeDefault = typeof typeDefault !== 'undefined' + if (!hasTypeDefault) { + typeDefault = [false, true, null, StringType, ArrayType] + } + var remove = {} - var typeDefault = [false, true, null, StringType, ArrayType] Object.keys(data).forEach(function (k) { if (k === 'argv') { @@ -44,7 +48,8 @@ function clean (data, { types, typeDefs, invalidHandler }) { } var val = data[k] var isArray = Array.isArray(val) - var type = types[k] + let rawType = types[k] + var type = rawType if (!isArray) { val = [val] } @@ -82,7 +87,14 @@ function clean (data, { types, typeDefs, invalidHandler }) { } if (!Object.prototype.hasOwnProperty.call(types, k)) { - return v + if (!hasTypeDefault) { + return v + } + // if the default type has been passed in then we want to validate the + // unknown data key instead of bailing out earlier. we also set the raw + // type which is passed to the invalid handler so that it can be + // determined if during validation if it is unknown vs invalid + rawType = typeDefault } // allow `--no-blah` to set 'blah' to null if null is allowed @@ -93,16 +105,16 @@ function clean (data, { types, typeDefs, invalidHandler }) { var d = {} d[k] = v - debug('prevalidated val', d, v, types[k]) - if (!validate(d, k, v, types[k], { typeDefs })) { + debug('prevalidated val', d, v, rawType) + if (!validate(d, k, v, rawType, { typeDefs })) { if (invalidHandler) { - invalidHandler(k, v, types[k], data) + invalidHandler(k, v, rawType, data) } else if (invalidHandler !== false) { - debug('invalid: ' + k + '=' + v, types[k]) + debug('invalid: ' + k + '=' + v, rawType) } return remove } - debug('validated v', d, v, types[k]) + debug('validated v', d, v, rawType) return d[k] }).filter(function (v) { return v !== remove diff --git a/lib/nopt.js b/lib/nopt.js index 70fd809..a8f2764 100644 --- a/lib/nopt.js +++ b/lib/nopt.js @@ -1,5 +1,4 @@ const lib = require('./nopt-lib') -const defaultTypeDefs = require('./type-defs') // This is the version of nopt's API that requires setting typeDefs and invalidHandler // on the required `nopt` object since it is a singleton. To not do a breaking change @@ -9,7 +8,7 @@ const defaultTypeDefs = require('./type-defs') module.exports = exports = nopt exports.clean = clean -exports.typeDefs = defaultTypeDefs +exports.typeDefs = lib.typeDefs exports.lib = lib function nopt (types = {}, shorthands = {}, args = process.argv, slice = 2) { diff --git a/package.json b/package.json index fd0c0ff..2f30b28 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,9 @@ "tap": "^16.3.0" }, "tap": { - "lines": 91, - "branches": 87, - "statements": 91, + "lines": 92, + "branches": 86, + "statements": 92, "nyc-arg": [ "--exclude", "tap-snapshots/**" diff --git a/test/type-default.js b/test/type-default.js new file mode 100644 index 0000000..dd5ea73 --- /dev/null +++ b/test/type-default.js @@ -0,0 +1,48 @@ +const t = require('tap') +const nopt = require('../lib/nopt-lib.js') + +t.test('use other type default', (t) => { + const NotAllowed = Symbol('NotAllowed') + const Invalid = Symbol('Invalid') + + const clean = (data, opts) => { + const invalids = [] + nopt.clean(data, { + types: { + str: nopt.typeDefs.String.type, + invalid: Invalid, + }, + typeDefs: { + ...nopt.typeDefs, + NotAllowed: { type: NotAllowed, validate: () => false }, + Invalid: { type: Invalid, validate: () => false }, + }, + invalidHandler: (k, v, type) => invalids.push([k, v, type]), + ...opts, + }) + return { + keys: Object.keys(data), + invalids, + } + } + + t.strictSame(clean({ + str: 'aaa', + invalid: 'bad', + unknown: 'huh?', + }), { + keys: ['str', 'unknown'], + invalids: [['invalid', 'bad', Invalid]], + }, 'invalid data is removed with clean') + + t.strictSame(clean({ + str: 'aaa', + invalid: 'bad', + unknown: 'huh?', + }, { typeDefault: NotAllowed }), { + keys: ['str'], + invalids: [['invalid', 'bad', Invalid], ['unknown', 'huh?', NotAllowed]], + }, 'invalid and unknown data is removed with a custom typeDefault') + + t.end() +})