Skip to content

Commit 9f1fb30

Browse files
JessNahJessica Nahulanljharb
authored
Fleshing out parseArgs to support withValue and Multiples options (#9)
* 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 <ljharb@gmail.com> * chore(parseargs): optimize line 48 following suggestion Co-authored-by: Jordan Harband <ljharb@gmail.com> * chore(parseargs): optimize lines 61 and 66 following suggestion Co-authored-by: Jessica Nahulan <JessicaN@ibm.com> Co-authored-by: Jordan Harband <ljharb@gmail.com>
1 parent 0f40fa0 commit 9f1fb30

2 files changed

Lines changed: 91 additions & 10 deletions

File tree

index.js

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ const parseArgs = (
77
if (typeof options !== 'object' || options === null) {
88
throw new Error('Whoops!')
99
}
10+
if (options.withValue !== undefined && !Array.isArray(options.withValue)) {
11+
throw new Error('Whoops! options.withValue should be an array.')
12+
}
1013

1114
let result = {
1215
args: {},
@@ -23,7 +26,6 @@ const parseArgs = (
2326
// and is returned verbatim
2427
if (arg === '--') {
2528
result.positionals.push(...argv.slice(++pos))
26-
2729
return result
2830
}
2931
// look for shortcodes: -fXzy
@@ -36,17 +38,42 @@ const parseArgs = (
3638
arg = arg.replace(/^-+/, '')
3739

3840
if (arg.includes('=')) {
41+
//withValue equals(=) case
3942
const argParts = arg.split('=')
4043

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

56+
result.args[arg] = true
57+
//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
58+
//ie. --foo b --bar c, after setting b as the value for foo, evaluate --bar next and skip 'b'
59+
const val = options.withValue && options.withValue.includes(arg) ? argv[++pos] : undefined
60+
//Append value to previous arg values array for case of multiples option, else add to empty array
61+
result.values[arg] = [].concat(
62+
options.multiples && options.multiples.includes(arg) && result.values[arg] ? result.values[arg] : [],
63+
val)
64+
} else {
65+
//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
66+
result.args[arg] = true
67+
//Append undefined to previous arg values array for case of multiples option, else add to empty array
68+
result.values[arg] = [].concat(
69+
options.multiples && options.multiples.includes(arg) && result.values[arg] ? result.values[arg] : [],
70+
undefined
71+
)
4972
}
73+
74+
} else {
75+
//Arguements without a dash prefix are considered "positional"
76+
result.positionals.push(arg)
5077
}
5178

5279
pos++

test/index.js

Lines changed: 57 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
const test = require('tape')
44
const {parseArgs} = require('../index.js')
55

6+
//Test results are as we expect
7+
68
test('Everything after a bare `--` is considered a positional argument', function (t) {
79
const passedArgs = ['--', 'barepositionals', 'mopositionals']
810
const expected = { args: {}, values: {}, positionals: ['barepositionals', 'mopositionals'] }
@@ -15,22 +17,74 @@ test('Everything after a bare `--` is considered a positional argument', functio
1517

1618
test('args are true', function (t) {
1719
const passedArgs = ['--foo', '--bar']
18-
const expected = { args: { foo: true, bar: true}, values: {}, positionals: [] }
20+
const expected = { args: { foo: true, bar: true}, values: {foo: [undefined], bar: [undefined]}, positionals: [] }
1921
const args = parseArgs(passedArgs)
2022

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

2325
t.end()
2426
})
2527

28+
test('arg is true and positional is identified', function (t) {
29+
const passedArgs = ['--foo=a', '--foo', 'b']
30+
const expected = { args: { foo: true}, values: { foo: [undefined]}, positionals: ['b'] }
31+
const args = parseArgs(passedArgs)
32+
33+
t.deepEqual(args, expected, 'arg is true and positional is identified')
34+
35+
t.end()
36+
})
37+
2638
test('args equals are passed "withValue"', function (t) {
2739
const passedArgs = ['--so=wat']
28-
const passedOptions = { withValue: true }
29-
const expected = { args: { so: true}, values: { so: "wat"}, positionals: [] }
40+
const passedOptions = { withValue: ['so'] }
41+
const expected = { args: { so: true}, values: { so: ["wat"]}, positionals: [] }
3042
const args = parseArgs(passedArgs, passedOptions)
3143

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

3446
t.end()
3547
})
3648

49+
test('same arg is passed twice "withValue" and last value is recorded', function (t) {
50+
const passedArgs = ['--foo=a', '--foo', 'b']
51+
const passedOptions = { withValue: ['foo'] }
52+
const expected = { args: { foo: true}, values: { foo: ['b']}, positionals: [] }
53+
const args = parseArgs(passedArgs, passedOptions)
54+
55+
t.deepEqual(args, expected, 'last arg value is passed')
56+
57+
t.end()
58+
})
59+
60+
test('args are passed "withValue" and "multiples"', function (t) {
61+
const passedArgs = ['--foo=a', '--foo', 'b']
62+
const passedOptions = { withValue: ['foo'], multiples: ['foo'] }
63+
const expected = { args: { foo: true}, values: { foo: ['a', 'b']}, positionals: [] }
64+
const args = parseArgs(passedArgs, passedOptions)
65+
66+
t.deepEqual(args, expected, 'both arg values are passed')
67+
68+
t.end()
69+
})
70+
71+
72+
//Test bad inputs
73+
74+
test('boolean passed to "withValue" option', function (t) {
75+
const passedArgs = ['--so=wat']
76+
const passedOptions = { withValue: true }
77+
78+
t.throws(function() { parseArgs(passedArgs, passedOptions) });
79+
80+
t.end()
81+
})
82+
83+
test('string passed to "withValue" option', function (t) {
84+
const passedArgs = ['--so=wat']
85+
const passedOptions = { withValue: 'so' }
86+
87+
t.throws(function() { parseArgs(passedArgs, passedOptions) });
88+
89+
t.end()
90+
})

0 commit comments

Comments
 (0)