Skip to content

Commit

Permalink
feat(commands): implemented variadic positional arguments
Browse files Browse the repository at this point in the history
  • Loading branch information
bcoe committed Apr 4, 2016
1 parent 066972b commit 51d926e
Show file tree
Hide file tree
Showing 3 changed files with 91 additions and 10 deletions.
11 changes: 11 additions & 0 deletions README.md
Expand Up @@ -530,6 +530,17 @@ yargs.command('get <source> [proxy]', 'make a get HTTP request')
.argv
```

#### Variadic Positional Arguments

The last positional argument can optionally accept an array of
values, by using the `..` operator:

```js
yargs.command('download <url> [files..]', 'download several files')
.help()
.argv
```

### Providing a Command Module

For complicated commands you can pull the logic into a module. A module
Expand Down
31 changes: 23 additions & 8 deletions lib/command.js
Expand Up @@ -45,15 +45,26 @@ module.exports = function (yargs, usage, validation) {

function parseCommand (cmd) {
var splitCommand = cmd.split(/\s/)
var bregex = /[\][<>]/g
var bregex = /\.*[\][<>]/g
var parsedCommand = {
cmd: (splitCommand.shift()).replace(bregex, ''),
demanded: [],
optional: []
}
splitCommand.forEach(function (cmd) {
if (/^\[/.test(cmd)) parsedCommand.optional.push(cmd.replace(bregex, ''))
else parsedCommand.demanded.push(cmd.replace(bregex, ''))
splitCommand.forEach(function (cmd, i) {
var variadic = false
if (/\.+[\]>]/.test(cmd) && i === splitCommand.length - 1) variadic = true
if (/^\[/.test(cmd)) {
parsedCommand.optional.push({
cmd: cmd.replace(bregex, ''),
variadic: variadic
})
} else {
parsedCommand.demanded.push({
cmd: cmd.replace(bregex, ''),
variadic: variadic
})
}
})
return parsedCommand
}
Expand Down Expand Up @@ -113,15 +124,19 @@ module.exports = function (yargs, usage, validation) {
validation.positionalCount(demanded.length, argv._.length)

while (demanded.length) {
if (!argv._.length) break
var demand = demanded.shift()
argv[demand] = argv._.shift()
if (demand.variadic) argv[demand.cmd] = []
if (!argv._.length) break
if (demand.variadic) argv[demand.cmd] = argv._.splice(0)
else argv[demand.cmd] = argv._.shift()
}

while (optional.length) {
if (!argv._.length) break
var maybe = optional.shift()
argv[maybe] = argv._.shift()
if (maybe.variadic) argv[maybe.cmd] = []
if (!argv._.length) break
if (maybe.variadic) argv[maybe.cmd] = argv._.splice(0)
else argv[maybe.cmd] = argv._.shift()
}

argv._ = context.commands.concat(argv._)
Expand Down
59 changes: 57 additions & 2 deletions test/command.js
Expand Up @@ -17,8 +17,14 @@ describe('Command', function () {
})
var command = y.getCommandInstance()
var handlers = command.getCommandHandlers()
handlers.foo.demanded.should.include('bar')
handlers.foo.optional.should.include('awesome')
handlers.foo.demanded.should.include({
cmd: 'bar',
variadic: false
})
handlers.foo.optional.should.include({
cmd: 'awesome',
variadic: false
})
})

it('populates inner argv with positional arguments', function (done) {
Expand Down Expand Up @@ -60,6 +66,55 @@ describe('Command', function () {
})
})

describe('variadic', function () {
it('allows required arguments to be variadic', function () {
var argv = yargs('foo /root file1 file2 file3')
.command('foo <root> <files..>')
.argv

argv.root.should.equal('/root')
argv.files.should.deep.equal(['file1', 'file2', 'file3'])
})

it('allows optional arguments to be variadic', function () {
var argv = yargs('foo /root file1 file2 file3')
.command('foo <root> [files..]')
.argv

argv.root.should.equal('/root')
argv.files.should.deep.equal(['file1', 'file2', 'file3'])
})

it('fails if required arguments are missing', function (done) {
yargs('foo /root')
.command('foo <root> <files..>')
.fail(function (err) {
err.should.match(/Not enough non-option arguments/)
return done()
})
.argv
})

it('does not fail if zero optional arguments are provided', function () {
var argv = yargs('foo /root')
.command('foo <root> [files...]')
.argv

argv.root.should.equal('/root')
argv.files.should.deep.equal([])
})

it('only allows the last argument to be variadic', function () {
var argv = yargs('foo /root file1 file2')
.command('foo <root..> <file>')
.argv

argv.root.should.equal('/root')
argv.file.should.equal('file1')
argv._.should.include('file2')
})
})

describe('missing positional arguments', function () {
it('fails if a required argument is missing', function (done) {
var argv = yargs('foo hello')
Expand Down

0 comments on commit 51d926e

Please sign in to comment.