Skip to content

Commit

Permalink
Fleshing out parseArgs to support withValue and Multiples options (#9)
Browse files Browse the repository at this point in the history
* feat(parseArgs): set positionals and handle withValue with multiples options

This PR handles:
1. setting values for args only if the arg is specified in the options.withValue array
2. set arg value to undefined if withValue isn't specified
3. add args without a dashprefix to the positionals array
4. handle only recording the last value for the arg, in the case of multiple values set for same arg
5. in the case of multiple values set for the same arg, and 'multiples' options having been set, handle recording all values for the arg in the returned array
6. Introduces new test cases covering readme examples, withValue, multiples, and error throwing bad input cases

* Revert "feat(parseArgs): set positionals and handle withValue with multiples options"

This reverts commit 4486551.

* feat(parseArgs): set positionals and handle withValue with multiples

* chore: add more comments

* chore(parse-args): resolve type checking on withValue to handle false,0,null,nan,'' cases

* chore(parseargs): fix formatting on line 10

Co-authored-by: Jordan Harband <[email protected]>

* chore(parseargs): optimize line 48 following suggestion

Co-authored-by: Jordan Harband <[email protected]>

* chore(parseargs): optimize lines 61 and 66 following suggestion

Co-authored-by: Jessica Nahulan <[email protected]>
Co-authored-by: Jordan Harband <[email protected]>
  • Loading branch information
3 people authored Oct 4, 2021
1 parent 0f40fa0 commit 9f1fb30
Show file tree
Hide file tree
Showing 2 changed files with 91 additions and 10 deletions.
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()
})

0 comments on commit 9f1fb30

Please sign in to comment.