Skip to content

Commit abdc7da

Browse files
MattSturgeonbcoe
authored andcommitted
feat: allow implies and conflicts to accept array values (#922)
1 parent a9f03e7 commit abdc7da

File tree

4 files changed

+122
-45
lines changed

4 files changed

+122
-45
lines changed

docs/api.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,8 @@ Where `nyc-babel-config` is a package that exports configuration in its index.
403403
<a name="conflicts"></a>.conflicts(x, y)
404404
----------------------------------------------
405405

406-
Given the key `x` is set, the key `y` must not be set.
406+
Given the key `x` is set, the key `y` must not be set. `y` can either be a single
407+
string or an array of argument names that `x` conflicts with.
407408

408409
Optionally `.conflicts()` can accept an object specifying multiple conflicting keys.
409410

@@ -798,7 +799,9 @@ var yargs = require("yargs")(['--info'])
798799
<a name="implies"></a>.implies(x, y)
799800
--------------
800801

801-
Given the key `x` is set, it is required that the key `y` is set.
802+
Given the key `x` is set, it is required that the key `y` is set. `y` can either
803+
be the name of an argument to imply, a number indicating a number indicating the
804+
position of an argument or an array of multiple implications to associate with `x`.
802805

803806
Optionally `.implies()` can accept an object specifying multiple implications.
804807

lib/validation.js

Lines changed: 69 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
'use strict'
2+
const argsert = require('./argsert')
23
const objFilter = require('./obj-filter')
34
const specialKeys = ['$0', '--', '_']
45

@@ -228,13 +229,22 @@ module.exports = function validation (yargs, usage, y18n) {
228229
// check implications, argument foo implies => argument bar.
229230
let implied = {}
230231
self.implies = function implies (key, value) {
232+
argsert('<string|object> [array|number|string]', [key, value], arguments.length)
233+
231234
if (typeof key === 'object') {
232235
Object.keys(key).forEach((k) => {
233236
self.implies(k, key[k])
234237
})
235238
} else {
236239
yargs.global(key)
237-
implied[key] = value
240+
if (!implied[key]) {
241+
implied[key] = []
242+
}
243+
if (Array.isArray(value)) {
244+
value.forEach((i) => self.implies(key, i))
245+
} else {
246+
implied[key].push(value)
247+
}
238248
}
239249
}
240250
self.getImplied = function getImplied () {
@@ -245,48 +255,55 @@ module.exports = function validation (yargs, usage, y18n) {
245255
const implyFail = []
246256

247257
Object.keys(implied).forEach((key) => {
248-
let num
249-
const origKey = key
250-
let value = implied[key]
251-
252-
// convert string '1' to number 1
253-
num = Number(key)
254-
key = isNaN(num) ? key : num
255-
256-
if (typeof key === 'number') {
257-
// check length of argv._
258-
key = argv._.length >= key
259-
} else if (key.match(/^--no-.+/)) {
260-
// check if key doesn't exist
261-
key = key.match(/^--no-(.+)/)[1]
262-
key = !argv[key]
263-
} else {
264-
// check if key exists
265-
key = argv[key]
266-
}
258+
if (implied[key]) {
259+
implied[key].forEach((value) => {
260+
let num
261+
const origKey = key
262+
263+
// convert string '1' to number 1
264+
num = Number(key)
265+
key = isNaN(num) ? key : num
266+
267+
if (typeof key === 'number') {
268+
// check length of argv._
269+
key = argv._.length >= key
270+
} else if (key.match(/^--no-.+/)) {
271+
// check if key doesn't exist
272+
key = key.match(/^--no-(.+)/)[1]
273+
key = !argv[key]
274+
} else {
275+
// check if key exists
276+
key = argv[key]
277+
}
267278

268-
num = Number(value)
269-
value = isNaN(num) ? value : num
279+
num = Number(value)
280+
value = isNaN(num) ? value : num
270281

271-
if (typeof value === 'number') {
272-
value = argv._.length >= value
273-
} else if (value.match(/^--no-.+/)) {
274-
value = value.match(/^--no-(.+)/)[1]
275-
value = !argv[value]
276-
} else {
277-
value = argv[value]
278-
}
282+
if (typeof value === 'number') {
283+
value = argv._.length >= value
284+
} else if (value.match(/^--no-.+/)) {
285+
value = value.match(/^--no-(.+)/)[1]
286+
value = !argv[value]
287+
} else {
288+
value = argv[value]
289+
}
279290

280-
if (key && !value) {
281-
implyFail.push(origKey)
291+
if (key && !value) {
292+
implyFail.push(origKey)
293+
}
294+
})
282295
}
283296
})
284297

285298
if (implyFail.length) {
286299
let msg = `${__('Implications failed:')}\n`
287300

288301
implyFail.forEach((key) => {
289-
msg += (` ${key} -> ${implied[key]}`)
302+
if (implied[key]) {
303+
implied[key].forEach((value) => {
304+
msg += (` ${key} -> ${value}`)
305+
})
306+
}
290307
})
291308

292309
usage.fail(msg)
@@ -295,24 +312,36 @@ module.exports = function validation (yargs, usage, y18n) {
295312

296313
let conflicting = {}
297314
self.conflicts = function conflicts (key, value) {
315+
argsert('<string|object> [array|string]', [key, value], arguments.length)
316+
298317
if (typeof key === 'object') {
299318
Object.keys(key).forEach((k) => {
300319
self.conflicts(k, key[k])
301320
})
302321
} else {
303322
yargs.global(key)
304-
conflicting[key] = value
323+
if (!conflicting[key]) {
324+
conflicting[key] = []
325+
}
326+
if (Array.isArray(value)) {
327+
value.forEach((i) => self.conflicts(key, i))
328+
} else {
329+
conflicting[key].push(value)
330+
}
305331
}
306332
}
307333
self.getConflicting = () => conflicting
308334

309335
self.conflicting = function conflictingFn (argv) {
310-
const args = Object.getOwnPropertyNames(argv)
311-
args.forEach((arg) => {
312-
// we default keys to 'undefined' that have been configured, we should not
313-
// apply conflicting check unless they are a value other than 'undefined'.
314-
if (conflicting[arg] && args.indexOf(conflicting[arg]) !== -1 && argv[arg] !== undefined && argv[conflicting[arg]] !== undefined) {
315-
usage.fail(__('Arguments %s and %s are mutually exclusive', arg, conflicting[arg]))
336+
Object.keys(argv).forEach((key) => {
337+
if (conflicting[key]) {
338+
conflicting[key].forEach((value) => {
339+
// we default keys to 'undefined' that have been configured, we should not
340+
// apply conflicting check unless they are a value other than 'undefined'.
341+
if (value && argv[key] !== undefined && argv[value] !== undefined) {
342+
usage.fail(__(`Arguments ${key} and ${value} are mutually exclusive`))
343+
}
344+
})
316345
}
317346
})
318347
}

test/validation.js

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,35 @@ describe('validation tests', () => {
3838
.argv
3939
})
4040

41+
it('fails if either implied argument is not set', (done) => {
42+
let fail1 = false
43+
let fail2 = false
44+
yargs(['--foo', '-a'])
45+
.boolean('foo')
46+
.implies({
47+
'foo': ['a', 'b']
48+
})
49+
.fail((msg1) => {
50+
fail1 = true
51+
yargs(['--foo', '-b'])
52+
.boolean('foo')
53+
.implies({
54+
'foo': ['a', 'b']
55+
})
56+
.fail((msg2) => {
57+
fail2 = true
58+
msg1.should.match(/Implications failed/)
59+
msg2.should.match(/Implications failed/)
60+
return done()
61+
})
62+
.argv
63+
})
64+
.argv
65+
// Prevent timeouts
66+
fail1.should.be.true
67+
fail2.should.be.true
68+
})
69+
4170
it("fails if --no-foo's implied argument is not set", (done) => {
4271
yargs([])
4372
.implies({
@@ -150,9 +179,25 @@ describe('validation tests', () => {
150179
.argv
151180
})
152181

182+
it('fails if argument is supplied along with either conflicting argument', (done) => {
183+
yargs(['-f', '-b'])
184+
.conflicts('f', ['b', 'c'])
185+
.fail((msg1) => {
186+
yargs(['-f', '-c'])
187+
.conflicts('f', ['b', 'c'])
188+
.fail((msg2) => {
189+
msg1.should.equal('Arguments f and b are mutually exclusive')
190+
msg2.should.equal('Arguments f and c are mutually exclusive')
191+
return done()
192+
})
193+
.argv
194+
})
195+
.argv
196+
})
197+
153198
it('should not fail if no conflicting arguments are provided', () => {
154199
yargs(['-b', '-c'])
155-
.conflicts('f', 'b')
200+
.conflicts('f', ['b', 'c'])
156201
.fail((msg) => {
157202
expect.fail()
158203
})

yargs.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -416,13 +416,13 @@ function Yargs (processArgs, cwd, parentRequire) {
416416
}
417417

418418
self.implies = function (key, value) {
419-
argsert('<string|object> [string]', [key, value], arguments.length)
419+
argsert('<string|object> [number|string|array]', [key, value], arguments.length)
420420
validation.implies(key, value)
421421
return self
422422
}
423423

424424
self.conflicts = function (key1, key2) {
425-
argsert('<string|object> [string]', [key1, key2], arguments.length)
425+
argsert('<string|object> [string|array]', [key1, key2], arguments.length)
426426
validation.conflicts(key1, key2)
427427
return self
428428
}

0 commit comments

Comments
 (0)