diff --git a/lib/debug.js b/lib/debug.js index 194d0c6..e62198e 100644 --- a/lib/debug.js +++ b/lib/debug.js @@ -1,6 +1,4 @@ /* istanbul ignore next */ module.exports = process.env.DEBUG_NOPT || process.env.NOPT_DEBUG - ? function () { - console.error.apply(console, arguments) - } - : function () {} + ? (...a) => console.error(...a) + : () => {} diff --git a/lib/nopt-lib.js b/lib/nopt-lib.js index 85f1521..b39664a 100644 --- a/lib/nopt-lib.js +++ b/lib/nopt-lib.js @@ -1,4 +1,4 @@ -var abbrev = require('abbrev') +const abbrev = require('abbrev') const debug = require('./debug') const defaultTypeDefs = require('./type-defs') @@ -17,11 +17,22 @@ const getType = (k, { types, dynamicTypes }) => { return [hasType, type] } -function nopt (args, { types, dynamicTypes, shorthands, typeDefs, invalidHandler, typeDefault }) { +const isTypeDef = (type, def) => def && type === def +const hasTypeDef = (type, def) => def && type.indexOf(def) !== -1 +const doesNotHaveTypeDef = (type, def) => def && type.indexOf(def) === -1 + +function nopt (args, { + types, + shorthands, + typeDefs, + invalidHandler, + typeDefault, + dynamicTypes, +} = {}) { debug(types, shorthands, args, typeDefs) - var data = {} - var argv = { + const data = {} + const argv = { remain: [], cooked: args, original: args.slice(0), @@ -43,35 +54,48 @@ function nopt (args, { types, dynamicTypes, shorthands, typeDefs, invalidHandler return data } -function clean (data, { types, dynamicTypes, 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 +function clean (data, { + types = {}, + typeDefs = {}, + dynamicTypes, + 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] + typeDefault = [false, true, null] + if (StringType) { + typeDefault.push(StringType) + } + if (ArrayType) { + typeDefault.push(ArrayType) + } } - var remove = {} + const remove = {} - Object.keys(data).forEach(function (k) { + Object.keys(data).forEach((k) => { if (k === 'argv') { return } - var val = data[k] - var isArray = Array.isArray(val) + let val = data[k] + debug('val=%j', val) + const isArray = Array.isArray(val) let [hasType, rawType] = getType(k, { types, dynamicTypes }) - var type = rawType + let type = rawType if (!isArray) { val = [val] } if (!type) { type = typeDefault } - if (type === ArrayType) { + if (isTypeDef(type, ArrayType)) { type = typeDefault.concat(ArrayType) } if (!Array.isArray(type)) { @@ -80,22 +104,22 @@ function clean (data, { types, dynamicTypes, typeDefs, invalidHandler, typeDefau debug('val=%j', val) debug('types=', type) - val = val.map(function (v) { + val = val.map((v) => { // if it's an unknown value, then parse false/true/null/numbers/dates if (typeof v === 'string') { debug('string %j', v) v = v.trim() if ((v === 'null' && ~type.indexOf(null)) || (v === 'true' && - (~type.indexOf(true) || ~type.indexOf(BooleanType))) + (~type.indexOf(true) || hasTypeDef(type, BooleanType))) || (v === 'false' && - (~type.indexOf(false) || ~type.indexOf(BooleanType)))) { + (~type.indexOf(false) || hasTypeDef(type, BooleanType)))) { v = JSON.parse(v) debug('jsonable %j', v) - } else if (~type.indexOf(NumberType) && !isNaN(v)) { + } else if (hasTypeDef(type, NumberType) && !isNaN(v)) { debug('convert to number', v) v = +v - } else if (~type.indexOf(DateType) && !isNaN(Date.parse(v))) { + } else if (hasTypeDef(type, DateType) && !isNaN(Date.parse(v))) { debug('convert to date', v) v = new Date(v) } @@ -114,11 +138,11 @@ function clean (data, { types, dynamicTypes, typeDefs, invalidHandler, typeDefau // allow `--no-blah` to set 'blah' to null if null is allowed if (v === false && ~type.indexOf(null) && - !(~type.indexOf(false) || ~type.indexOf(BooleanType))) { + !(~type.indexOf(false) || hasTypeDef(type, BooleanType))) { v = null } - var d = {} + const d = {} d[k] = v debug('prevalidated val', d, v, rawType) if (!validate(d, k, v, rawType, { typeDefs })) { @@ -131,13 +155,11 @@ function clean (data, { types, dynamicTypes, typeDefs, invalidHandler, typeDefau } debug('validated v', d, v, rawType) return d[k] - }).filter(function (v) { - return v !== remove - }) + }).filter((v) => v !== remove) // if we allow Array specifically, then an empty array is how we // express 'no value here', not null. Allow it. - if (!val.length && type.indexOf(ArrayType) === -1) { + if (!val.length && doesNotHaveTypeDef(type, ArrayType)) { debug('VAL HAS NO LENGTH, DELETE IT', val, k, type.indexOf(ArrayType)) delete data[k] } else if (isArray) { @@ -151,12 +173,12 @@ function clean (data, { types, dynamicTypes, typeDefs, invalidHandler, typeDefau }) } -function validate (data, k, val, type, { typeDefs }) { - const ArrayType = typeDefs.Array.type +function validate (data, k, val, type, { typeDefs } = {}) { + const ArrayType = typeDefs?.Array?.type // arrays are lists of types. if (Array.isArray(type)) { for (let i = 0, l = type.length; i < l; i++) { - if (type[i] === ArrayType) { + if (isTypeDef(type[i], ArrayType)) { continue } if (validate(data, k, val, type[i], { typeDefs })) { @@ -168,7 +190,7 @@ function validate (data, k, val, type, { typeDefs }) { } // an array of anything? - if (type === ArrayType) { + if (isTypeDef(type, ArrayType)) { return true } @@ -193,17 +215,17 @@ function validate (data, k, val, type, { typeDefs }) { } // now go through the list of typeDefs, validate against each one. - var ok = false - var types = Object.keys(typeDefs) + let ok = false + const types = Object.keys(typeDefs) for (let i = 0, l = types.length; i < l; i++) { debug('test type %j %j %j', k, val, types[i]) - var t = typeDefs[types[i]] + const t = typeDefs[types[i]] if (t && ( (type && type.name && t.type && t.type.name) ? (type.name === t.type.name) : (type === t.type) )) { - var d = {} + const d = {} ok = t.validate(d, k, val) !== false val = d[k] if (ok) { @@ -220,20 +242,25 @@ function validate (data, k, val, type, { typeDefs }) { return ok } -function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands }) { - const StringType = typeDefs.String.type - const NumberType = typeDefs.String.type - const ArrayType = typeDefs.Array.type - const BooleanType = typeDefs.Boolean.type +function parse (args, data, remain, { + types = {}, + typeDefs = {}, + shorthands = {}, + dynamicTypes, +} = {}) { + const StringType = typeDefs.String?.type + const NumberType = typeDefs.Number?.type + const ArrayType = typeDefs.Array?.type + const BooleanType = typeDefs.Boolean?.type debug('parse', args, data, remain) - var abbrevs = abbrev(Object.keys(types)) + const abbrevs = abbrev(Object.keys(types)) debug('abbrevs=%j', abbrevs) - var shortAbbr = abbrev(Object.keys(shorthands)) + const shortAbbr = abbrev(Object.keys(shorthands)) - for (var i = 0; i < args.length; i++) { - var arg = args[i] + for (let i = 0; i < args.length; i++) { + let arg = args[i] debug('arg', arg) if (arg.match(/^-{2,}$/)) { @@ -243,22 +270,21 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands args[i] = '--' break } - var hadEq = false + let hadEq = false if (arg.charAt(0) === '-' && arg.length > 1) { - var at = arg.indexOf('=') + const at = arg.indexOf('=') if (at > -1) { hadEq = true - var v = arg.slice(at + 1) + const v = arg.slice(at + 1) arg = arg.slice(0, at) args.splice(i, 1, arg, v) } // see if it's a shorthand // if so, splice and back up to re-parse it. - var shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands }) + const shRes = resolveShort(arg, shortAbbr, abbrevs, { shorthands }) debug('arg=%j shRes=%j', arg, shRes) if (shRes) { - debug(arg, shRes) args.splice.apply(args, [i, 1].concat(shRes)) if (arg !== shRes[0]) { i-- @@ -266,7 +292,7 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands } } arg = arg.replace(/^-+/, '') - var no = null + let no = null while (arg.toLowerCase().indexOf('no-') === 0) { no = !no arg = arg.slice(3) @@ -276,15 +302,15 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands arg = abbrevs[arg] } - var [hasType, argType] = getType(arg, { types, dynamicTypes }) - var isTypeArray = Array.isArray(argType) + let [hasType, argType] = getType(arg, { types, dynamicTypes }) + let isTypeArray = Array.isArray(argType) if (isTypeArray && argType.length === 1) { isTypeArray = false argType = argType[0] } - var isArray = argType === ArrayType || - isTypeArray && argType.indexOf(ArrayType) !== -1 + let isArray = isTypeDef(argType, ArrayType) || + isTypeArray && hasTypeDef(argType, ArrayType) // allow unknown things to be arrays if specified multiple times. if (!hasType && hasOwn(data, arg)) { @@ -294,12 +320,12 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands isArray = true } - var val - var la = args[i + 1] + let val + let la = args[i + 1] - var isBool = typeof no === 'boolean' || - argType === BooleanType || - isTypeArray && argType.indexOf(BooleanType) !== -1 || + const isBool = typeof no === 'boolean' || + isTypeDef(argType, BooleanType) || + isTypeArray && hasTypeDef(argType, BooleanType) || (typeof argType === 'undefined' && !hadEq) || (la === 'false' && (argType === null || @@ -330,11 +356,11 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands i++ } else if (!la.match(/^-{2,}[^-]/) && !isNaN(la) && - ~argType.indexOf(NumberType)) { + hasTypeDef(argType, NumberType)) { // number val = +la i++ - } else if (!la.match(/^-[^-]/) && ~argType.indexOf(StringType)) { + } else if (!la.match(/^-[^-]/) && hasTypeDef(argType, StringType)) { // string val = la i++ @@ -350,7 +376,7 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands continue } - if (argType === StringType) { + if (isTypeDef(argType, StringType)) { if (la === undefined) { la = '' } else if (la.match(/^-{1,2}[^-]+/)) { @@ -378,7 +404,26 @@ function parse (args, data, remain, { typeDefs, types, dynamicTypes, shorthands } } -function resolveShort (arg, shortAbbr, abbrevs, { shorthands }) { +const SINGLES = Symbol('singles') +const singleCharacters = (arg, shorthands) => { + let singles = shorthands[SINGLES] + if (!singles) { + singles = Object.keys(shorthands).filter((s) => s.length === 1).reduce((l, r) => { + l[r] = true + return l + }, {}) + shorthands[SINGLES] = singles + debug('shorthand singles', singles) + } + const chrs = arg.split('').filter((c) => singles[c]) + return chrs.join('') === arg ? chrs : null +} + +function resolveShort (arg, ...rest) { + const { types = {}, shorthands = {} } = rest.length ? rest.pop() : {} + const shortAbbr = rest[0] ?? abbrev(Object.keys(shorthands)) + const abbrevs = rest[1] ?? abbrev(Object.keys(types)) + // handle single-char shorthands glommed together, like // npm ls -glp, but only if there is one dash, and only if // all of the chars are single-char shorthands, and it's @@ -401,28 +446,9 @@ function resolveShort (arg, shortAbbr, abbrevs, { shorthands }) { } // first check to see if this arg is a set of single-char shorthands - var singles = shorthands.___singles - if (!singles) { - singles = Object.keys(shorthands).filter(function (s) { - return s.length === 1 - }).reduce(function (l, r) { - l[r] = true - return l - }, {}) - shorthands.___singles = singles - debug('shorthand singles', singles) - } - - var chrs = arg.split('').filter(function (c) { - return singles[c] - }) - - if (chrs.join('') === arg) { - return chrs.map(function (c) { - return shorthands[c] - }).reduce(function (l, r) { - return l.concat(r) - }, []) + const chrs = singleCharacters(arg, shorthands) + if (chrs) { + return chrs.map((c) => shorthands[c]).reduce((l, r) => l.concat(r), []) } // if it's an arg abbrev, and not a literal shorthand, then prefer the arg diff --git a/lib/nopt.js b/lib/nopt.js index 70fd809..37f01a0 100644 --- a/lib/nopt.js +++ b/lib/nopt.js @@ -12,9 +12,9 @@ exports.clean = clean exports.typeDefs = defaultTypeDefs exports.lib = lib -function nopt (types = {}, shorthands = {}, args = process.argv, slice = 2) { +function nopt (types, shorthands, args = process.argv, slice = 2) { return lib.nopt(args.slice(slice), { - types, + types: types || {}, shorthands: shorthands || {}, typeDefs: exports.typeDefs, invalidHandler: exports.invalidHandler, @@ -23,7 +23,7 @@ function nopt (types = {}, shorthands = {}, args = process.argv, slice = 2) { function clean (data, types, typeDefs = exports.typeDefs) { return lib.clean(data, { - types, + types: types || {}, typeDefs, invalidHandler: exports.invalidHandler, }) diff --git a/lib/type-defs.js b/lib/type-defs.js index 6acf5e0..608352e 100644 --- a/lib/type-defs.js +++ b/lib/type-defs.js @@ -1,7 +1,7 @@ -var url = require('url') -var path = require('path') -var Stream = require('stream').Stream -var os = require('os') +const url = require('url') +const path = require('path') +const Stream = require('stream').Stream +const os = require('os') const debug = require('./debug') function validateString (data, k, val) { @@ -18,9 +18,9 @@ function validatePath (data, k, val) { val = String(val) - var isWin = process.platform === 'win32' - var homePattern = isWin ? /^~(\/|\\)/ : /^~\// - var home = os.homedir() + const isWin = process.platform === 'win32' + const homePattern = isWin ? /^~(\/|\\)/ : /^~\// + const home = os.homedir() if (home && val.match(homePattern)) { data[k] = path.resolve(home, val.slice(2)) @@ -39,7 +39,7 @@ function validateNumber (data, k, val) { } function validateDate (data, k, val) { - var s = Date.parse(val) + const s = Date.parse(val) debug('validate Date %j %j %j', k, val, s) if (isNaN(s)) { return false diff --git a/package.json b/package.json index 99cc9f2..a4384d2 100644 --- a/package.json +++ b/package.json @@ -30,9 +30,6 @@ "tap": "^16.3.0" }, "tap": { - "statements": 94, - "branches": 88, - "lines": 94, "nyc-arg": [ "--exclude", "tap-snapshots/**" diff --git a/test/basic.js b/test/basic.js index 60e462b..0f5b738 100644 --- a/test/basic.js +++ b/test/basic.js @@ -1,107 +1,116 @@ -var nopt = require('../') -var t = require('tap') -var isWin = process.platform === 'win32' +const nopt = require('../') +const t = require('tap') +const isWin = process.platform === 'win32' -t.test('empty array is fine if type includes Array', function (t) { - var typeDefs = { +t.test('empty array is fine if type includes Array', t => { + const types = { arr: [Array, String], } - var data = { + const data = { arr: [], } - nopt.clean(data, typeDefs) + nopt.clean(data, types) t.same(data.arr, []) t.end() }) -t.test('passing a string results in a string', function (t) { - var parsed = nopt({ key: String }, {}, ['--key', 'myvalue'], 0) +t.test('passing a string results in a string', t => { + const parsed = nopt({ key: String }, {}, ['--key', 'myvalue'], 0) t.same(parsed.key, 'myvalue') t.end() }) // https://github.com/npm/nopt/issues/31 -t.test('Empty String results in empty string, not true', function (t) { - var parsed = nopt({ empty: String }, {}, ['--empty'], 0) +t.test('Empty String results in empty string, not true', t => { + const parsed = nopt({ empty: String }, {}, ['--empty'], 0) t.same(parsed.empty, '') t.end() }) // https://github.com/npm/nopt/issues/65 -t.test('Empty String should not swallow next flag', function (t) { - var parsed = nopt({ empty: String, foo: String }, {}, ['--empty', '--foo'], 0) +t.test('Empty String should not swallow next flag', t => { + const parsed = nopt({ empty: String, foo: String }, {}, ['--empty', '--foo'], 0) t.same(parsed.empty, '') t.same(parsed.foo, '') t.end() }) // https://github.com/npm/nopt/issues/66 -t.test('Empty String should not be true when type is single item Array', function (t) { - var parsed = nopt({ foo: [String] }, {}, ['--foo'], 0) +t.test('Empty String should not be true when type is single item Array', t => { + const parsed = nopt({ foo: [String] }, {}, ['--foo'], 0) t.same(parsed.foo, '') t.end() }) -t.test('~ path is resolved to ' + (isWin ? '%USERPROFILE%' : '$HOME'), function (t) { - var path = require('path') - var the - - if (isWin) { - the = { +t.test('~ path is resolved to ' + (isWin ? '%USERPROFILE%' : '$HOME'), t => { + const path = require('path') + const the = isWin + ? { key: 'USERPROFILE', dir: 'C:\\temp', val: '~\\val', - } - } else { - the = { + } : { key: 'HOME', dir: '/tmp', val: '~/val', } - } + if (!process.env[the.key]) { process.env[the.key] = the.dir } - var parsed = nopt({ key: path }, {}, ['--key=' + the.val], 0) + const parsed = nopt({ key: path }, {}, ['--key=' + the.val], 0) t.same(parsed.key, path.resolve(process.env[the.key], 'val')) t.end() }) // https://github.com/npm/nopt/issues/24 -t.test('Unknown options are not parsed as numbers', function (t) { - var parsed = nopt({ 'parse-me': Number }, null, ['--leave-as-is=1.20', '--parse-me=1.20'], 0) +t.test('Unknown options are not parsed as numbers', t => { + const parsed = nopt({ 'parse-me': Number }, null, ['--leave-as-is=1.20', '--parse-me=1.20'], 0) t.equal(parsed['leave-as-is'], '1.20') t.equal(parsed['parse-me'], 1.2) t.end() }) // https://github.com/npm/nopt/issues/48 -t.test('Check types based on name of type', function (t) { - var parsed = nopt({ 'parse-me': { name: 'Number' } }, null, ['--parse-me=1.20'], 0) +t.test('Check types based on name of type', t => { + const parsed = nopt({ 'parse-me': { name: 'Number' } }, null, ['--parse-me=1.20'], 0) t.equal(parsed['parse-me'], 1.2) t.end() }) -t.test('Missing types are not parsed', function (t) { - var parsed = nopt({ 'parse-me': {} }, null, ['--parse-me=1.20'], 0) +t.test('Missing types are not parsed', t => { + const parsed = nopt({ 'parse-me': {} }, null, ['--parse-me=1.20'], 0) // should only contain argv t.equal(Object.keys(parsed).length, 1) t.end() }) -t.test('Types passed without a name are not parsed', function (t) { - var parsed = nopt({ 'parse-me': {} }, {}, ['--parse-me=1.20'], 0) +t.test('Types passed without a name are not parsed', t => { + const parsed = nopt({ 'parse-me': {} }, {}, ['--parse-me=1.20'], 0) // should only contain argv t.equal(Object.keys(parsed).length, 1) t.end() }) -t.test('other tests', function (t) { - var Stream = require('stream') - var path = require('path') - var url = require('url') +t.test('no types does not throw', t => { + const parsed = nopt(null, null, ['--leave-as-is=1.20'], 0) + t.equal(parsed['leave-as-is'], '1.20') + t.end() +}) + +t.test('clean: no types does not throw', t => { + const data = { 'leave-unknown': 'still here' } + nopt.clean(data) + t.strictSame(data, { 'leave-unknown': 'still here' }) + t.end() +}) + +t.test('other tests', t => { + const Stream = require('stream') + const path = require('path') + const url = require('url') - var shorthands = + const shorthands = { s: ['--loglevel', 'silent'], d: ['--loglevel', 'info'], dd: ['--loglevel', 'verbose'], @@ -126,7 +135,7 @@ t.test('other tests', function (t) { g: ['--global'], } - var types = + const types = { aoa: Array, nullstream: [null, Stream], date: Date, @@ -261,6 +270,12 @@ t.test('other tests', function (t) { ['--date 2011-01-25', { date: new Date('2011-01-25') }, []], + ['--date xxxxxxxxxx', // invalid date -> NaN + {}, + []], + ['--registry https://github.com', + { registry: 'https://github.com/' }, + []], ['-cl 1', { config: true, length: 1 }, [], @@ -293,15 +308,15 @@ t.test('other tests', function (t) { { path: process.cwd() }, []], ].forEach(function (params) { - var argv = params[0].split(/\s+/) - var opts = params[1] - var rem = params[2] - var actual = nopt(params[3] || types, params[4] || shorthands, argv, 0) - var parsed = actual.argv + const argv = params[0].split(/\s+/) + const opts = params[1] + const rem = params[2] + const actual = nopt(params[3] || types, params[4] || shorthands, argv, 0) + const parsed = actual.argv delete actual.argv - for (var i in opts) { - var e = JSON.stringify(opts[i]) - var a = JSON.stringify(actual[i] === undefined ? null : actual[i]) + for (const i in opts) { + const e = JSON.stringify(opts[i]) + const a = JSON.stringify(actual[i] === undefined ? null : actual[i]) if (e && typeof e === 'object') { t.same(e, a) } else { @@ -313,13 +328,13 @@ t.test('other tests', function (t) { t.end() }) -t.test('argv toString()', function (t) { - var parsed = nopt({ key: String }, {}, ['--key', 'myvalue'], 0) +t.test('argv toString()', t => { + const parsed = nopt({ key: String }, {}, ['--key', 'myvalue'], 0) t.same(parsed.argv.toString(), '"--key" "myvalue"') t.end() }) -t.test('custom invalidHandler', function (t) { +t.test('custom invalidHandler', t => { t.teardown(() => { delete nopt.invalidHandler }) @@ -331,32 +346,46 @@ t.test('custom invalidHandler', function (t) { nopt({ key: Number }, {}, ['--key', 'nope'], 0) }) -t.test('numbered boolean', function (t) { - var parsed = nopt({ key: [Boolean, String] }, {}, ['--key', '0'], 0) +t.test('numbered boolean', t => { + const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', '0'], 0) t.same(parsed.key, false) t.end() }) -t.test('false string boolean', function (t) { - var parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'false'], 0) +t.test('false string boolean', t => { + const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'false'], 0) t.same(parsed.key, false) t.end() }) -t.test('true string boolean', function (t) { - var parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'true'], 0) +t.test('true string boolean', t => { + const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'true'], 0) t.same(parsed.key, true) t.end() }) -t.test('null string boolean', function (t) { - var parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'null'], 0) +t.test('null string boolean', t => { + const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'null'], 0) t.same(parsed.key, false) t.end() }) -t.test('other string boolean', function (t) { - var parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'yes'], 0) +t.test('other string boolean', t => { + const parsed = nopt({ key: [Boolean, String] }, {}, ['--key', 'yes'], 0) + t.same(parsed.key, true) + t.end() +}) + +t.test('number boolean', t => { + const parsed = nopt({ key: [Boolean, Number] }, {}, ['--key', '100'], 0) t.same(parsed.key, true) t.end() }) + +t.test('no args', (t) => { + const _argv = process.argv + t.teardown(() => process.argv = _argv) + process.argv = ['', '', 'a'] + t.strictSame(nopt(), { argv: { remain: ['a'], cooked: ['a'], original: ['a'] } }) + t.end() +}) diff --git a/test/lib.js b/test/lib.js new file mode 100644 index 0000000..7027f5d --- /dev/null +++ b/test/lib.js @@ -0,0 +1,155 @@ +const t = require('tap') +const noptLib = require('../lib/nopt-lib.js') +const Stream = require('stream') + +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) + } + t.end() +} + +t.test('stream', t => { + nopt(t, { x: new Stream.Readable() }, { types: { x: Stream } }, {}) +}) + +t.test('no/missing options', t => { + t.doesNotThrow(() => noptLib.nopt([])) + t.doesNotThrow(() => noptLib.nopt([], {})) + t.doesNotThrow(() => noptLib.clean({})) + t.doesNotThrow(() => noptLib.clean({}, {})) + t.doesNotThrow(() => noptLib.parse([])) + t.doesNotThrow(() => noptLib.parse([], {}, [], {})) + t.doesNotThrow(() => noptLib.validate({})) + t.doesNotThrow(() => noptLib.validate({}, null, null, null, {})) + t.doesNotThrow(() => noptLib.resolveShort('')) + t.doesNotThrow(() => noptLib.resolveShort('', {})) + t.doesNotThrow(() => noptLib.resolveShort('', {}, {}, {})) + t.end() +}) + +t.test('key argv is ignored', (t) => { + nopt(t, ['--argvv', '--argv'], {}, { + argvv: true, + argv: { + remain: [], + cooked: ['--argvv', '--argv'], + original: ['--argvv', '--argv'], + }, + }) +}) + +t.test('boolean with null', (t) => { + nopt(t, ['--boolNull', 'null', '--boolOnly', 'null'], { + types: { + boolNull: [Boolean, null], + boolOnly: [Boolean], + }, + }, { + boolNull: false, + boolOnly: true, + argv: { + remain: ['null'], + cooked: ['--boolNull', 'null', '--boolOnly', 'null'], + original: ['--boolNull', 'null', '--boolOnly', 'null'], + }, + }) +}) + +t.test('-- after non string type', (t) => { + nopt(t, ['--x', '5', '--y', '--', '200'], { + types: { + x: Number, + y: Number, + }, + }, { + x: 5, + // XXX: getting coverage to 100 and this seems wrong + // test matches current implementation for now + y: 1, + argv: { + remain: ['200'], + cooked: ['--x', '5', '--y', '--', '200'], + original: ['--x', '5', '--y', '--', '200'], + }, + }) +}) + +t.test('nan', (t) => { + nopt(t, ['--x', '5'], { + types: { + x: NaN, + }, + }, { + x: undefined, + argv: { + remain: [], + cooked: ['--x', '5'], + original: ['--x', '5'], + }, + }) +}) + +t.test('string/null', (t) => { + nopt(t, ['--x', 'null', '--y', 'false', '--z', 'true'], { + types: { + x: Number, + y: Number, + z: Number, + }, + }, { + argv: { + remain: [], + cooked: ['--x', 'null', '--y', 'false', '--z', 'true'], + original: ['--x', 'null', '--y', 'false', '--z', 'true'], + }, + }) +}) + +t.test('false invalid handler', (t) => { + // this is only for coverage + nopt(t, ['--x', 'null'], { + types: { + x: Number, + }, + invalidHandler: false, + }, { + argv: { + remain: [], + cooked: ['--x', 'null'], + original: ['--x', 'null'], + }, + }) +}) + +t.test('shorthands that is the same', (t) => { + nopt(t, ['--sh'], { + types: {}, + shorthands: { + sh: '--sh', + }, + }, { + sh: true, + argv: { + remain: [], + cooked: ['--sh'], + original: ['--sh'], + }, + }) +}) + +t.test('unknown multiple', (t) => { + nopt(t, ['--mult', '--mult', '--mult'], { + types: {}, + }, { + mult: [true, true, true], + argv: { + remain: [], + cooked: ['--mult', '--mult', '--mult'], + original: ['--mult', '--mult', '--mult'], + }, + }) +}) diff --git a/test/resolve-short.js b/test/resolve-short.js new file mode 100644 index 0000000..562d6b0 --- /dev/null +++ b/test/resolve-short.js @@ -0,0 +1,21 @@ +const t = require('tap') +const nopt = require('../lib/nopt-lib.js') + +t.test('basic', (t) => { + const assertShort = (v, expected) => { + const k = 'package-lock' + t.strictSame(nopt.resolveShort(v, { + shorthands: { shrinkwrap: `--${k} true` }, + types: { [k]: Boolean }, + }), expected !== undefined ? expected : [`--${k}`, 'true'], v) + } + + assertShort('--shrinkwrap') + assertShort('--shrinkwra') + assertShort('-shrinkwra') + assertShort('shr') + assertShort('--package-lock', null) + assertShort('--pa', null) + + t.end() +}) diff --git a/test/type-defs.js b/test/type-defs.js new file mode 100644 index 0000000..e5be680 --- /dev/null +++ b/test/type-defs.js @@ -0,0 +1,29 @@ +const t = require('tap') +const path = require('path') +const os = require('os') + +const nopt = (t, argv, opts, expected) => { + const mockNopt = t.mock('../lib/nopt-lib.js', { + '../lib/type-defs.js': t.mock('../lib/type-defs.js'), + }) + t.match( + mockNopt.nopt(argv, { typeDefs: mockNopt.typeDefs, ...opts }), + expected + ) + t.end() +} + +t.test('path + null', t => { + nopt(t, ['--x', 'null'], { types: { x: [path, null] } }, { + x: undefined, + }) +}) + +t.test('win32 path', t => { + const platform = Object.getOwnPropertyDescriptor(process, 'platform') + t.teardown(() => Object.defineProperty(process, 'platform', platform)) + Object.defineProperty(process, 'platform', { ...platform, value: 'win32' }) + nopt(t, ['--x', '~/y'], { types: { x: path } }, { + x: `${os.homedir()}/y`, + }) +})