Skip to content

Commit 94b5188

Browse files
committed
feat: add unknownHandler and expansionHandler
This is in favor of proc-log
1 parent bd7f53a commit 94b5188

File tree

5 files changed

+163
-41
lines changed

5 files changed

+163
-41
lines changed

README.md

+9-8
Original file line numberDiff line numberDiff line change
@@ -141,14 +141,15 @@ config object and remove its invalid properties.
141141

142142
## Error Handling
143143

144-
By default, nopt outputs a warning to standard error when invalid values for
145-
known options are found. You can change this behavior by assigning a method
146-
to `nopt.invalidHandler`. This method will be called with
147-
the offending `nopt.invalidHandler(key, val, types)`.
148-
149-
If no `nopt.invalidHandler` is assigned, then it will console.error
150-
its whining. If it is assigned to boolean `false` then the warning is
151-
suppressed.
144+
By default nopt logs debug messages if `DEBUG_NOPT` or `NOPT_DEBUG` are set in the environment.
145+
146+
You can assign the following methods to `nopt` for a more granular notification of invalid, unknown, and expanding options:
147+
148+
`nopt.invalidHandler(key, value, type, data)` - Called when a value is invalid for its option.
149+
`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.
150+
`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)
151+
152+
You can also set any of these to `false` to disable the debugging messages that they generate.
152153

153154
## Abbreviations
154155

lib/nopt-lib.js

+36-10
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
const abbrev = require('abbrev')
22
const debug = require('./debug')
33
const defaultTypeDefs = require('./type-defs')
4-
const { log } = require('proc-log')
54

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

@@ -26,7 +25,9 @@ function nopt (args, {
2625
types,
2726
shorthands,
2827
typeDefs,
29-
invalidHandler,
28+
invalidHandler, // opt is configured but its value does not validate against given type
29+
unknownHandler, // opt is not configured
30+
expansionHandler, // opt is being expanded via abbrev or shorthand
3031
typeDefault,
3132
dynamicTypes,
3233
} = {}) {
@@ -39,7 +40,9 @@ function nopt (args, {
3940
original: args.slice(0),
4041
}
4142

42-
parse(args, data, argv.remain, { typeDefs, types, dynamicTypes, shorthands })
43+
parse(args, data, argv.remain, {
44+
typeDefs, types, dynamicTypes, shorthands, unknownHandler, expansionHandler,
45+
})
4346

4447
// now data is full
4548
clean(data, { types, dynamicTypes, typeDefs, invalidHandler, typeDefault })
@@ -248,6 +251,8 @@ function parse (args, data, remain, {
248251
typeDefs = {},
249252
shorthands = {},
250253
dynamicTypes,
254+
unknownHandler,
255+
expansionHandler,
251256
} = {}) {
252257
const StringType = typeDefs.String?.type
253258
const NumberType = typeDefs.Number?.type
@@ -286,6 +291,11 @@ function parse (args, data, remain, {
286291
const shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands })
287292
debug('arg=%j shRes=%j', arg, shRes)
288293
if (shRes) {
294+
if (expansionHandler) {
295+
expansionHandler(arg, shRes)
296+
} else if (expansionHandler !== false) {
297+
debug(`expansion: ${arg} -> ${shRes}`)
298+
}
289299
args.splice.apply(args, [i, 1].concat(shRes))
290300
if (arg !== shRes[0]) {
291301
i--
@@ -301,8 +311,11 @@ function parse (args, data, remain, {
301311

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

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

338-
if (typeof argType === 'undefined' && !hadEq && la && !la?.startsWith('-')) {
339-
// npm itself will log the warning about the undefined argType
340-
log.warn(`"${la}" is being parsed as a normal command line argument.`)
351+
if (typeof argType === 'undefined') {
352+
// la is not being parsed as a value for this arg
353+
const hangingLa = !hadEq && la && !la?.startsWith('-')
354+
if (!hadEq && la && !la?.startsWith('-')) {
355+
if (unknownHandler) {
356+
if (hangingLa && !['true', 'false'].includes(la)) {
357+
// la is going to unexpectedly be parsed outside the context of this arg
358+
unknownHandler(arg, la)
359+
} else {
360+
unknownHandler(arg)
361+
}
362+
} else if (unknownHandler !== false) {
363+
debug(`unknown: ${arg}`)
364+
if (hangingLa && !['true', 'false'].includes(la)) {
365+
debug(`unknown: ${la} parsed as normal opt`)
366+
}
367+
}
368+
}
341369
}
342370

343371
if (isBool) {
@@ -467,8 +495,6 @@ function resolveShort (arg, ...rest) {
467495

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

lib/nopt.js

+4
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@ function nopt (types, shorthands, args = process.argv, slice = 2) {
1818
shorthands: shorthands || {},
1919
typeDefs: exports.typeDefs,
2020
invalidHandler: exports.invalidHandler,
21+
unknownHandler: exports.unknownHandler,
22+
expansionHandler: exports.expansionHandler,
2123
})
2224
}
2325

@@ -26,5 +28,7 @@ function clean (data, types, typeDefs = exports.typeDefs) {
2628
types: types || {},
2729
typeDefs,
2830
invalidHandler: exports.invalidHandler,
31+
unknownHandler: exports.unknownHandler,
32+
expansionHandler: exports.expansionHandler,
2933
})
3034
}

test/basic.js

+48
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,54 @@ t.test('custom invalidHandler', t => {
346346
nopt({ key: Number }, {}, ['--key', 'nope'], 0)
347347
})
348348

349+
t.test('custom unknownHandler string', t => {
350+
t.teardown(() => {
351+
delete nopt.unknownHandler
352+
})
353+
nopt.unknownHandler = (k, next) => {
354+
t.match(k, 'x')
355+
t.match(next, 'null')
356+
t.end()
357+
}
358+
nopt({}, {}, ['--x', 'null'], 0)
359+
})
360+
361+
t.test('custom unknownHandler boolean', t => {
362+
t.teardown(() => {
363+
delete nopt.unknownHandler
364+
})
365+
nopt.unknownHandler = (k, next) => {
366+
t.match(k, 'x')
367+
t.match(next, undefined)
368+
t.end()
369+
}
370+
nopt({}, {}, ['--x', 'false'], 0)
371+
})
372+
373+
t.test('custom expansionHandler shorthand', t => {
374+
t.teardown(() => {
375+
delete nopt.expansionHandler
376+
})
377+
nopt.expansionHandler = (short, long) => {
378+
t.match(short, '--shor')
379+
t.match(long, ['--shorthand'])
380+
t.end()
381+
}
382+
nopt({}, { short: '--shorthand' }, ['--shor'], 0)
383+
})
384+
385+
t.test('custom expansionHandler abbrev', t => {
386+
t.teardown(() => {
387+
delete nopt.expansionHandler
388+
})
389+
nopt.expansionHandler = (short, long) => {
390+
t.match(short, 'shor')
391+
t.match(long, 'shorthand')
392+
t.end()
393+
}
394+
nopt({ shorthand: Boolean }, {}, ['--short', 'true'], 0)
395+
})
396+
349397
t.test('numbered boolean', t => {
350398
const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', '0'], 0)
351399
t.same(parsed.key, false)

test/lib.js

+66-23
Original file line numberDiff line numberDiff line change
@@ -2,24 +2,13 @@ const t = require('tap')
22
const noptLib = require('../lib/nopt-lib.js')
33
const Stream = require('stream')
44

5-
const logs = []
6-
t.afterEach(() => {
7-
logs.length = 0
8-
})
9-
process.on('log', (...msg) => {
10-
logs.push(msg)
11-
})
12-
13-
const nopt = (t, argv, opts, expected, expectedLogs) => {
5+
const nopt = (t, argv, opts, expected) => {
146
if (Array.isArray(argv)) {
157
t.strictSame(noptLib.nopt(argv, { typeDefs: noptLib.typeDefs, ...opts }), expected)
168
} else {
179
noptLib.clean(argv, { typeDefs: noptLib.typeDefs, ...opts })
1810
t.match(argv, expected)
1911
}
20-
if (expectedLogs) {
21-
t.match(expectedLogs, logs)
22-
}
2312
t.end()
2413
}
2514

@@ -136,6 +125,68 @@ t.test('false invalid handler', (t) => {
136125
})
137126
})
138127

128+
t.test('false unknown handler string', (t) => {
129+
// this is only for coverage
130+
nopt(t, ['--x', 'null'], {
131+
unknownHandler: false,
132+
}, {
133+
x: true,
134+
argv: {
135+
remain: ['null'],
136+
cooked: ['--x', 'null'],
137+
original: ['--x', 'null'],
138+
},
139+
})
140+
})
141+
142+
t.test('default unknown handler opt', (t) => {
143+
// this is only for coverage
144+
nopt(t, ['--x', '--y'], {}, {
145+
x: true,
146+
y: true,
147+
argv: {
148+
remain: [],
149+
cooked: ['--x', '--y'],
150+
original: ['--x', '--y'],
151+
},
152+
})
153+
})
154+
155+
t.test('false expansion handler shorthand', (t) => {
156+
// this is only for coverage
157+
nopt(t, ['--shor'], {
158+
types: {},
159+
expansionHandler: false,
160+
shorthands: {
161+
short: '--shorthand',
162+
},
163+
}, {
164+
shorthand: true,
165+
argv: {
166+
remain: [],
167+
cooked: ['--shorthand'],
168+
original: ['--shor'],
169+
},
170+
})
171+
})
172+
173+
t.test('false expansion handler abbrev', (t) => {
174+
// this is only for coverage
175+
nopt(t, ['--short', 'true'], {
176+
types: {
177+
shorthand: Boolean,
178+
},
179+
expansionHandler: false,
180+
}, {
181+
shorthand: true,
182+
argv: {
183+
remain: [],
184+
cooked: ['--short', 'true'],
185+
original: ['--short', 'true'],
186+
},
187+
})
188+
})
189+
139190
t.test('longhand abbreviation', (t) => {
140191
nopt(t, ['--lon', 'text'], {
141192
types: {
@@ -148,10 +199,7 @@ t.test('longhand abbreviation', (t) => {
148199
cooked: ['--lon', 'text'],
149200
original: ['--lon', 'text'],
150201
},
151-
}, [
152-
/* eslint-disable-next-line max-len */
153-
['warn', 'Expanding "--lon" to "--long". This will stop working in the next major version of npm.'],
154-
])
202+
})
155203
})
156204

157205
t.test('shorthand abbreviation', (t) => {
@@ -167,10 +215,7 @@ t.test('shorthand abbreviation', (t) => {
167215
cooked: ['--shorthand'],
168216
original: ['--shor'],
169217
},
170-
}, [
171-
/* eslint-disable-next-line max-len */
172-
['warn', 'Expanding "--shor" to "--short". This will stop working in the next major version of npm.'],
173-
])
218+
})
174219
})
175220

176221
t.test('shorthands that is the same', (t) => {
@@ -199,7 +244,5 @@ t.test('unknown multiple', (t) => {
199244
cooked: ['--mult', '--mult', '--mult', 'extra'],
200245
original: ['--mult', '--mult', '--mult', 'extra'],
201246
},
202-
}, [
203-
['warn', '"extra" is being parsed as a normal command line argument.'],
204-
])
247+
})
205248
})

0 commit comments

Comments
 (0)