Skip to content

Commit

Permalink
feat: add unknownHandler and expansionHandler
Browse files Browse the repository at this point in the history
This is in favor of proc-log
  • Loading branch information
wraithgar committed Jan 8, 2025
1 parent bd7f53a commit 94b5188
Show file tree
Hide file tree
Showing 5 changed files with 163 additions and 41 deletions.
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,14 +141,15 @@ config object and remove its invalid properties.

## Error Handling

By default, nopt outputs a warning to standard error when invalid values for
known options are found. You can change this behavior by assigning a method
to `nopt.invalidHandler`. This method will be called with
the offending `nopt.invalidHandler(key, val, types)`.

If no `nopt.invalidHandler` is assigned, then it will console.error
its whining. If it is assigned to boolean `false` then the warning is
suppressed.
By default nopt logs debug messages if `DEBUG_NOPT` or `NOPT_DEBUG` are set in the environment.

You can assign the following methods to `nopt` for a more granular notification of invalid, unknown, and expanding options:

`nopt.invalidHandler(key, value, type, data)` - Called when a value is invalid for its option.
`nopt.unknownHandler(key, next)` - Called when an option is found that has no configuration. In certain situations the next option on the command line will be parsed on its own instead of as part of the unknown option. In this case `next` will contain that option.
`nopt.expansionHandler(short, long)` - Called either when an option is automatically translated via inferring abbreviations, or explicitly translated via configured shorthands. (See Abbreviations and Shorthands below)

You can also set any of these to `false` to disable the debugging messages that they generate.

## Abbreviations

Expand Down
46 changes: 36 additions & 10 deletions lib/nopt-lib.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
const abbrev = require('abbrev')
const debug = require('./debug')
const defaultTypeDefs = require('./type-defs')
const { log } = require('proc-log')

const hasOwn = (o, k) => Object.prototype.hasOwnProperty.call(o, k)

Expand All @@ -26,7 +25,9 @@ function nopt (args, {
types,
shorthands,
typeDefs,
invalidHandler,
invalidHandler, // opt is configured but its value does not validate against given type
unknownHandler, // opt is not configured
expansionHandler, // opt is being expanded via abbrev or shorthand
typeDefault,
dynamicTypes,
} = {}) {
Expand All @@ -39,7 +40,9 @@ function nopt (args, {
original: args.slice(0),
}

parse(args, data, argv.remain, { typeDefs, types, dynamicTypes, shorthands })
parse(args, data, argv.remain, {
typeDefs, types, dynamicTypes, shorthands, unknownHandler, expansionHandler,
})

// now data is full
clean(data, { types, dynamicTypes, typeDefs, invalidHandler, typeDefault })
Expand Down Expand Up @@ -248,6 +251,8 @@ function parse (args, data, remain, {
typeDefs = {},
shorthands = {},
dynamicTypes,
unknownHandler,
expansionHandler,
} = {}) {
const StringType = typeDefs.String?.type
const NumberType = typeDefs.Number?.type
Expand Down Expand Up @@ -286,6 +291,11 @@ function parse (args, data, remain, {
const shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands })
debug('arg=%j shRes=%j', arg, shRes)
if (shRes) {
if (expansionHandler) {
expansionHandler(arg, shRes)
} else if (expansionHandler !== false) {
debug(`expansion: ${arg} -> ${shRes}`)
}
args.splice.apply(args, [i, 1].concat(shRes))
if (arg !== shRes[0]) {
i--
Expand All @@ -301,8 +311,11 @@ function parse (args, data, remain, {

// abbrev includes the original full string in its abbrev list
if (abbrevs[arg] && abbrevs[arg] !== arg) {
/* eslint-disable-next-line max-len */
log.warn(`Expanding "--${arg}" to "--${abbrevs[arg]}". This will stop working in the next major version of npm.`)
if (expansionHandler) {
expansionHandler(arg, abbrevs[arg])
} else if (expansionHandler !== false) {
debug(`expand: ${arg} -> ${abbrevs[arg]}`)
}
arg = abbrevs[arg]
}

Expand Down Expand Up @@ -335,9 +348,24 @@ function parse (args, data, remain, {
(argType === null ||
isTypeArray && ~argType.indexOf(null)))

if (typeof argType === 'undefined' && !hadEq && la && !la?.startsWith('-')) {
// npm itself will log the warning about the undefined argType
log.warn(`"${la}" is being parsed as a normal command line argument.`)
if (typeof argType === 'undefined') {
// la is not being parsed as a value for this arg
const hangingLa = !hadEq && la && !la?.startsWith('-')
if (!hadEq && la && !la?.startsWith('-')) {
if (unknownHandler) {
if (hangingLa && !['true', 'false'].includes(la)) {
// la is going to unexpectedly be parsed outside the context of this arg
unknownHandler(arg, la)
} else {
unknownHandler(arg)
}
} else if (unknownHandler !== false) {
debug(`unknown: ${arg}`)
if (hangingLa && !['true', 'false'].includes(la)) {
debug(`unknown: ${la} parsed as normal opt`)
}
}
}
}

if (isBool) {
Expand Down Expand Up @@ -467,8 +495,6 @@ function resolveShort (arg, ...rest) {

// if it's an abbr for a shorthand, then use that
if (shortAbbr[arg]) {
/* eslint-disable-next-line max-len */
log.warn(`Expanding "--${arg}" to "--${shortAbbr[arg]}". This will stop working in the next major version of npm.`)
arg = shortAbbr[arg]
}

Expand Down
4 changes: 4 additions & 0 deletions lib/nopt.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ function nopt (types, shorthands, args = process.argv, slice = 2) {
shorthands: shorthands || {},
typeDefs: exports.typeDefs,
invalidHandler: exports.invalidHandler,
unknownHandler: exports.unknownHandler,
expansionHandler: exports.expansionHandler,
})
}

Expand All @@ -26,5 +28,7 @@ function clean (data, types, typeDefs = exports.typeDefs) {
types: types || {},
typeDefs,
invalidHandler: exports.invalidHandler,
unknownHandler: exports.unknownHandler,
expansionHandler: exports.expansionHandler,
})
}
48 changes: 48 additions & 0 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,54 @@ t.test('custom invalidHandler', t => {
nopt({ key: Number }, {}, ['--key', 'nope'], 0)
})

t.test('custom unknownHandler string', t => {
t.teardown(() => {
delete nopt.unknownHandler
})
nopt.unknownHandler = (k, next) => {
t.match(k, 'x')
t.match(next, 'null')
t.end()
}
nopt({}, {}, ['--x', 'null'], 0)
})

t.test('custom unknownHandler boolean', t => {
t.teardown(() => {
delete nopt.unknownHandler
})
nopt.unknownHandler = (k, next) => {
t.match(k, 'x')
t.match(next, undefined)
t.end()
}
nopt({}, {}, ['--x', 'false'], 0)
})

t.test('custom expansionHandler shorthand', t => {
t.teardown(() => {
delete nopt.expansionHandler
})
nopt.expansionHandler = (short, long) => {
t.match(short, '--shor')
t.match(long, ['--shorthand'])
t.end()
}
nopt({}, { short: '--shorthand' }, ['--shor'], 0)
})

t.test('custom expansionHandler abbrev', t => {
t.teardown(() => {
delete nopt.expansionHandler
})
nopt.expansionHandler = (short, long) => {
t.match(short, 'shor')
t.match(long, 'shorthand')
t.end()
}
nopt({ shorthand: Boolean }, {}, ['--short', 'true'], 0)
})

t.test('numbered boolean', t => {
const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', '0'], 0)
t.same(parsed.key, false)
Expand Down
89 changes: 66 additions & 23 deletions test/lib.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,13 @@ const t = require('tap')
const noptLib = require('../lib/nopt-lib.js')
const Stream = require('stream')

const logs = []
t.afterEach(() => {
logs.length = 0
})
process.on('log', (...msg) => {
logs.push(msg)
})

const nopt = (t, argv, opts, expected, expectedLogs) => {
const nopt = (t, argv, opts, expected) => {
if (Array.isArray(argv)) {
t.strictSame(noptLib.nopt(argv, { typeDefs: noptLib.typeDefs, ...opts }), expected)
} else {
noptLib.clean(argv, { typeDefs: noptLib.typeDefs, ...opts })
t.match(argv, expected)
}
if (expectedLogs) {
t.match(expectedLogs, logs)
}
t.end()
}

Expand Down Expand Up @@ -136,6 +125,68 @@ t.test('false invalid handler', (t) => {
})
})

t.test('false unknown handler string', (t) => {
// this is only for coverage
nopt(t, ['--x', 'null'], {
unknownHandler: false,
}, {
x: true,
argv: {
remain: ['null'],
cooked: ['--x', 'null'],
original: ['--x', 'null'],
},
})
})

t.test('default unknown handler opt', (t) => {
// this is only for coverage
nopt(t, ['--x', '--y'], {}, {
x: true,
y: true,
argv: {
remain: [],
cooked: ['--x', '--y'],
original: ['--x', '--y'],
},
})
})

t.test('false expansion handler shorthand', (t) => {
// this is only for coverage
nopt(t, ['--shor'], {
types: {},
expansionHandler: false,
shorthands: {
short: '--shorthand',
},
}, {
shorthand: true,
argv: {
remain: [],
cooked: ['--shorthand'],
original: ['--shor'],
},
})
})

t.test('false expansion handler abbrev', (t) => {
// this is only for coverage
nopt(t, ['--short', 'true'], {
types: {
shorthand: Boolean,
},
expansionHandler: false,
}, {
shorthand: true,
argv: {
remain: [],
cooked: ['--short', 'true'],
original: ['--short', 'true'],
},
})
})

t.test('longhand abbreviation', (t) => {
nopt(t, ['--lon', 'text'], {
types: {
Expand All @@ -148,10 +199,7 @@ t.test('longhand abbreviation', (t) => {
cooked: ['--lon', 'text'],
original: ['--lon', 'text'],
},
}, [
/* eslint-disable-next-line max-len */
['warn', 'Expanding "--lon" to "--long". This will stop working in the next major version of npm.'],
])
})
})

t.test('shorthand abbreviation', (t) => {
Expand All @@ -167,10 +215,7 @@ t.test('shorthand abbreviation', (t) => {
cooked: ['--shorthand'],
original: ['--shor'],
},
}, [
/* eslint-disable-next-line max-len */
['warn', 'Expanding "--shor" to "--short". This will stop working in the next major version of npm.'],
])
})
})

t.test('shorthands that is the same', (t) => {
Expand Down Expand Up @@ -199,7 +244,5 @@ t.test('unknown multiple', (t) => {
cooked: ['--mult', '--mult', '--mult', 'extra'],
original: ['--mult', '--mult', '--mult', 'extra'],
},
}, [
['warn', '"extra" is being parsed as a normal command line argument.'],
])
})
})

0 comments on commit 94b5188

Please sign in to comment.