Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: 100 test coverage #126

Merged
merged 1 commit into from
Jun 14, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 2 additions & 4 deletions lib/debug.js
Original file line number Diff line number Diff line change
@@ -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)
: () => {}
198 changes: 112 additions & 86 deletions lib/nopt-lib.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
var abbrev = require('abbrev')
const abbrev = require('abbrev')
const debug = require('./debug')
const defaultTypeDefs = require('./type-defs')

Expand All @@ -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 && !hasTypeDef(type, def)

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),
Expand All @@ -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)) {
Expand All @@ -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)
}
Expand All @@ -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 })) {
Expand All @@ -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) {
Expand All @@ -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 })) {
Expand All @@ -168,7 +190,7 @@ function validate (data, k, val, type, { typeDefs }) {
}

// an array of anything?
if (type === ArrayType) {
if (isTypeDef(type, ArrayType)) {
return true
}

Expand All @@ -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) {
Expand All @@ -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,}$/)) {
Expand All @@ -243,30 +270,29 @@ 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--
continue
}
}
arg = arg.replace(/^-+/, '')
var no = null
let no = null
while (arg.toLowerCase().indexOf('no-') === 0) {
no = !no
arg = arg.slice(3)
Expand All @@ -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)) {
Expand All @@ -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 ||
Expand Down Expand Up @@ -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++
Expand All @@ -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}[^-]+/)) {
Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down
Loading