Skip to content

Commit 3319782

Browse files
committed
feat: add unknownHandler and abbrevHandler
This is in favor of proc-log
1 parent a7156b2 commit 3319782

File tree

5 files changed

+175
-52
lines changed

5 files changed

+175
-52
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.abbrevHandler(short, long)` - Called when an option is automatically translated via abbreviations.
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-12
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+
abbrevHandler, // opt is being expanded via abbrev
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, abbrevHandler,
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+
abbrevHandler,
251256
} = {}) {
252257
const StringType = typeDefs.String?.type
253258
const NumberType = typeDefs.Number?.type
@@ -283,7 +288,7 @@ function parse (args, data, remain, {
283288

284289
// see if it's a shorthand
285290
// if so, splice and back up to re-parse it.
286-
const shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands })
291+
const shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands, abbrevHandler })
287292
debug('arg=%j shRes=%j', arg, shRes)
288293
if (shRes) {
289294
args.splice.apply(args, [i, 1].concat(shRes))
@@ -301,8 +306,11 @@ function parse (args, data, remain, {
301306

302307
// abbrev includes the original full string in its abbrev list
303308
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.`)
309+
if (abbrevHandler) {
310+
abbrevHandler(arg, abbrevs[arg])
311+
} else if (abbrevHandler !== false) {
312+
debug(`abbrev: ${arg} -> ${abbrevs[arg]}`)
313+
}
306314
arg = abbrevs[arg]
307315
}
308316

@@ -335,9 +343,21 @@ function parse (args, data, remain, {
335343
(argType === null ||
336344
isTypeArray && ~argType.indexOf(null)))
337345

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.`)
346+
if (typeof argType === 'undefined') {
347+
// la is going to unexpectedly be parsed outside the context of this arg
348+
const hangingLa = !hadEq && la && !la?.startsWith('-') && !['true', 'false'].includes(la)
349+
if (unknownHandler) {
350+
if (hangingLa) {
351+
unknownHandler(arg, la)
352+
} else {
353+
unknownHandler(arg)
354+
}
355+
} else if (unknownHandler !== false) {
356+
debug(`unknown: ${arg}`)
357+
if (hangingLa) {
358+
debug(`unknown: ${la} parsed as normal opt`)
359+
}
360+
}
341361
}
342362

343363
if (isBool) {
@@ -429,7 +449,7 @@ const singleCharacters = (arg, shorthands) => {
429449
}
430450

431451
function resolveShort (arg, ...rest) {
432-
const { types = {}, shorthands = {} } = rest.length ? rest.pop() : {}
452+
const { abbrevHandler, types = {}, shorthands = {} } = rest.length ? rest.pop() : {}
433453
const shortAbbr = rest[0] ?? abbrev(Object.keys(shorthands))
434454
const abbrevs = rest[1] ?? abbrev(Object.keys(types))
435455

@@ -466,9 +486,13 @@ function resolveShort (arg, ...rest) {
466486
}
467487

468488
// if it's an abbr for a shorthand, then use that
489+
// exact match has already happened so we don't need to account for that here
469490
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.`)
491+
if (abbrevHandler) {
492+
abbrevHandler(arg, shortAbbr[arg])
493+
} else if (abbrevHandler !== false) {
494+
debug(`abbrev: ${arg} -> ${shortAbbr[arg]}`)
495+
}
472496
arg = shortAbbr[arg]
473497
}
474498

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+
abbrevHandler: exports.abbrevHandler,
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+
abbrevHandler: exports.abbrevHandler,
2933
})
3034
}

test/basic.js

+51
Original file line numberDiff line numberDiff line change
@@ -346,6 +346,57 @@ 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 normal abbrevHandler', t => {
374+
t.teardown(() => {
375+
delete nopt.abbrevHandler
376+
})
377+
nopt.abbrevHandler = (short, long) => {
378+
t.match(short, 'shor')
379+
t.match(long, 'shorthand')
380+
t.end()
381+
}
382+
nopt({ shorthand: Boolean }, {}, ['--short', 'true'], 0)
383+
})
384+
385+
t.test('custom shorthand abbrevHandler', t => {
386+
t.teardown(() => {
387+
delete nopt.abbrevHandler
388+
})
389+
nopt.abbrevHandler = (short, long) => {
390+
t.match(short, 'shor')
391+
t.match(long, 'shorthand')
392+
t.end()
393+
}
394+
nopt({
395+
longhand: Boolean,
396+
}, { shorthand: '--longhand' },
397+
['--short', 'true'], 0)
398+
})
399+
349400
t.test('numbered boolean', t => {
350401
const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', '0'], 0)
351402
t.same(parsed.key, false)

test/lib.js

+75-32
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,41 +125,97 @@ t.test('false invalid handler', (t) => {
136125
})
137126
})
138127

139-
t.test('longhand abbreviation', (t) => {
140-
nopt(t, ['--lon', 'text'], {
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 abbrev handler normal', (t) => {
156+
// this is only for coverage
157+
nopt(t, ['--long', 'true'], {
158+
types: {
159+
longhand: Boolean,
160+
},
161+
abbrevHandler: false,
162+
}, {
163+
longhand: true,
164+
argv: {
165+
remain: [],
166+
cooked: ['--long', 'true'],
167+
original: ['--long', 'true'],
168+
},
169+
})
170+
})
171+
172+
t.test('false abbrev handler shorthand', (t) => {
173+
// this is only for coverage
174+
nopt(t, ['--shor', 'true'], {
175+
types: {},
176+
shorthands: {
177+
short: '--longhand',
178+
},
179+
abbrevHandler: false,
180+
}, {
181+
longhand: true,
182+
argv: {
183+
remain: [],
184+
cooked: ['--longhand', 'true'],
185+
original: ['--shor', 'true'],
186+
},
187+
})
188+
})
189+
190+
t.test('normal abbreviation', (t) => {
191+
nopt(t, ['--shor', 'text'], {
141192
types: {
142-
long: String,
193+
shorthand: String,
143194
},
144195
}, {
145-
long: 'text',
196+
shorthand: 'text',
146197
argv: {
147198
remain: [],
148-
cooked: ['--lon', 'text'],
149-
original: ['--lon', 'text'],
199+
cooked: ['--shor', 'text'],
200+
original: ['--shor', '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) => {
158206
nopt(t, ['--shor'], {
159207
types: {},
160208
shorthands: {
161-
short: '--shorthand',
209+
short: '--longhand',
162210
},
163211
}, {
164-
shorthand: true,
212+
longhand: true,
165213
argv: {
166214
remain: [],
167-
cooked: ['--shorthand'],
215+
cooked: ['--longhand'],
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)