Skip to content

Commit

Permalink
fix: 100 test coverage (#126)
Browse files Browse the repository at this point in the history
  • Loading branch information
lukekarrys committed Jun 14, 2023
1 parent 008c672 commit 7f5b507
Show file tree
Hide file tree
Showing 9 changed files with 420 additions and 165 deletions.
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

0 comments on commit 7f5b507

Please sign in to comment.