Skip to content
41 changes: 34 additions & 7 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ const parseArgs = (
if (typeof options !== 'object' || options === null) {
throw new Error('Whoops!')
}
if (options.withValue !== undefined && !Array.isArray(options.withValue)) {
throw new Error('Whoops! options.withValue should be an array.')
}

let result = {
args: {},
Expand All @@ -23,7 +26,6 @@ const parseArgs = (
// and is returned verbatim
if (arg === '--') {
result.positionals.push(...argv.slice(++pos))

return result
}
// look for shortcodes: -fXzy
Expand All @@ -36,17 +38,42 @@ const parseArgs = (
arg = arg.replace(/^-+/, '')

if (arg.includes('=')) {
//withValue equals(=) case
const argParts = arg.split('=')

result.args[argParts[0]] = true
if (options.withValue) {
result.values[argParts[0]] = argParts[1]
}
}
else {
result.args[arg] = true
//If withValue option is specified, take 2nd part after '=' as value, else set value as undefined
const val = options.withValue && options.withValue.includes(argParts[0]) ? argParts[1] : undefined
//Append value to previous arg values array for case of multiples option, else add to empty array
result.values[argParts[0]] = [].concat(
options.multiples && options.multiples.includes(argParts[0]) && result.values[argParts[0]] || [],
val,
)
} else if (pos + 1 < argv.length && !argv[pos+1].startsWith('-')) {
//withValue option should also support setting values when '=' isn't used
//ie. both --foo=b and --foo b should work

result.args[arg] = true
//If withValue option is specified, take next position arguement as value and then increment pos so that we don't re-evaluate that arg, else set value as undefined
//ie. --foo b --bar c, after setting b as the value for foo, evaluate --bar next and skip 'b'
const val = options.withValue && options.withValue.includes(arg) ? argv[++pos] : undefined
//Append value to previous arg values array for case of multiples option, else add to empty array
result.values[arg] = [].concat(
options.multiples && options.multiples.includes(arg) && result.values[arg] ? result.values[arg] : [],
val)
} else {
//cases when an arg is specified without a value, example '--foo --bar' <- 'foo' and 'bar' args should be set to true and have value as undefined
result.args[arg] = true
//Append undefined to previous arg values array for case of multiples option, else add to empty array
result.values[arg] = [].concat(
options.multiples && options.multiples.includes(arg) && result.values[arg] ? result.values[arg] : [],
undefined
)
}

} else {
//Arguements without a dash prefix are considered "positional"
result.positionals.push(arg)
}

pos++
Expand Down
60 changes: 57 additions & 3 deletions test/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
const test = require('tape')
const {parseArgs} = require('../index.js')

//Test results are as we expect

test('Everything after a bare `--` is considered a positional argument', function (t) {
const passedArgs = ['--', 'barepositionals', 'mopositionals']
const expected = { args: {}, values: {}, positionals: ['barepositionals', 'mopositionals'] }
Expand All @@ -15,22 +17,74 @@ test('Everything after a bare `--` is considered a positional argument', functio

test('args are true', function (t) {
const passedArgs = ['--foo', '--bar']
const expected = { args: { foo: true, bar: true}, values: {}, positionals: [] }
const expected = { args: { foo: true, bar: true}, values: {foo: [undefined], bar: [undefined]}, positionals: [] }
const args = parseArgs(passedArgs)

t.deepEqual(args, expected, 'args are true')

t.end()
})

test('arg is true and positional is identified', function (t) {
const passedArgs = ['--foo=a', '--foo', 'b']
const expected = { args: { foo: true}, values: { foo: [undefined]}, positionals: ['b'] }
const args = parseArgs(passedArgs)

t.deepEqual(args, expected, 'arg is true and positional is identified')

t.end()
})

test('args equals are passed "withValue"', function (t) {
const passedArgs = ['--so=wat']
const passedOptions = { withValue: true }
const expected = { args: { so: true}, values: { so: "wat"}, positionals: [] }
const passedOptions = { withValue: ['so'] }
const expected = { args: { so: true}, values: { so: ["wat"]}, positionals: [] }
const args = parseArgs(passedArgs, passedOptions)

t.deepEqual(args, expected, 'arg value is passed')

t.end()
})

test('same arg is passed twice "withValue" and last value is recorded', function (t) {
const passedArgs = ['--foo=a', '--foo', 'b']
const passedOptions = { withValue: ['foo'] }
const expected = { args: { foo: true}, values: { foo: ['b']}, positionals: [] }
const args = parseArgs(passedArgs, passedOptions)

t.deepEqual(args, expected, 'last arg value is passed')

t.end()
})

test('args are passed "withValue" and "multiples"', function (t) {
const passedArgs = ['--foo=a', '--foo', 'b']
const passedOptions = { withValue: ['foo'], multiples: ['foo'] }
const expected = { args: { foo: true}, values: { foo: ['a', 'b']}, positionals: [] }
const args = parseArgs(passedArgs, passedOptions)

t.deepEqual(args, expected, 'both arg values are passed')

t.end()
})


//Test bad inputs

test('boolean passed to "withValue" option', function (t) {
const passedArgs = ['--so=wat']
const passedOptions = { withValue: true }

t.throws(function() { parseArgs(passedArgs, passedOptions) });

t.end()
})

test('string passed to "withValue" option', function (t) {
const passedArgs = ['--so=wat']
const passedOptions = { withValue: 'so' }

t.throws(function() { parseArgs(passedArgs, passedOptions) });

t.end()
})