Repository: yargs/yargs
Pull request #458 (2016-03-30 05:32:23)

variadic positional arguments
Introduces the concept of variadic commands arguments. I'm feeling like along with @nexdrew's work in https://github.com/yargs/yargs/pull/428 commands are getting pretty darn slick. You can now do: 

`./my-bin.js open file-1 file-2-file-3`

and get,

````js
{files: ['file-1', 'file-2', 'file-3']}
```

bcoe: reviewers: @nexdrew, @elas7, @lrlna 
coveralls: 
[![Coverage Status](https://coveralls.io/builds/5594083/badge)](https://coveralls.io/builds/5594083)

Coverage remained the same at 100.0% when pulling **4d2e37f1377cd048cff36d66b14f5b4358f89823 on variadic** into **a9fc82ff6665307f5a718a44f06e21923befe9e0 on master**.

elas7: @bcoe LGTM 
lrlna: @bcoe this is going to be :sparkles: brilliant :sparkles: 

Patch:
diff --git a/README.md b/README.md
index dc8b691..cf8fdce 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/lib/command.js b/lib/command.js
index 6e38b48..7665edc 100644
--- a/lib/command.js
+++ b/lib/command.js
@@ -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
   }
@@ -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._)


Test patch:
diff --git a/test/command.js b/test/command.js
index ec08c32..5f5b696 100644
--- a/test/command.js
+++ b/test/command.js
@@ -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) {
@@ -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')