Skip to content

Commit

Permalink
feat: add unknownHandler and abbrevHandler
Browse files Browse the repository at this point in the history
This is in favor of proc-log
  • Loading branch information
wraithgar committed Jan 10, 2025
1 parent bd7f53a commit e2c556d
Show file tree
Hide file tree
Showing 5 changed files with 128 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.abbrevHandler(short, long)` - Called when an option is automatically translated via abbreviations.

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

## Abbreviations

Expand Down
41 changes: 31 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
abbrevHandler, // 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, abbrevHandler,
})

// 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,
abbrevHandler,
} = {}) {
const StringType = typeDefs.String?.type
const NumberType = typeDefs.Number?.type
Expand Down Expand Up @@ -301,8 +306,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 (abbrevHandler) {
abbrevHandler(arg, abbrevs[arg])
} else if (abbrevHandler !== false) {
debug(`expand: ${arg} -> ${abbrevs[arg]}`)
}
arg = abbrevs[arg]
}

Expand Down Expand Up @@ -335,9 +343,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 +490,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,
abbrevHandler: exports.abbrevHandler,
})
}

Expand All @@ -26,5 +28,7 @@ function clean (data, types, typeDefs = exports.typeDefs) {
types: types || {},
typeDefs,
invalidHandler: exports.invalidHandler,
unknownHandler: exports.unknownHandler,
abbrevHandler: exports.abbrevHandler,
})
}
36 changes: 36 additions & 0 deletions test/basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,42 @@ 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 abbrevHandler', t => {
t.teardown(() => {
delete nopt.abbrevHandler
})
nopt.abbrevHandler = (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
71 changes: 48 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,50 @@ 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 abbrev handler', (t) => {
// this is only for coverage
nopt(t, ['--short', 'true'], {
types: {
shorthand: Boolean,
},
abbrevHandler: 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 +181,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 +197,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 +226,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 e2c556d

Please sign in to comment.