diff --git a/Readme.md b/Readme.md
index 76639bba5..f186852d5 100644
--- a/Readme.md
+++ b/Readme.md
@@ -38,6 +38,37 @@ console.log(' - %s cheese', program.cheese);
Short flags may be passed as a single arg, for example `-abc` is equivalent to `-a -b -c`. Multi-word options such as "--template-engine" are camel-cased, becoming `program.templateEngine` etc.
+## Variadic arguments
+
+ The last argument of a command can be variadic, and only the last argument. To make an argument variadic you have to
+ append `...` to the argument name. Here is an example:
+
+```js
+#!/usr/bin/env node
+
+/**
+ * Module dependencies.
+ */
+
+var program = require('commander');
+
+program
+ .version('0.0.1')
+ .command('rmdir
[otherDirs...]')
+ .action(function (dir, otherDirs) {
+ console.log('rmdir %s', dir);
+ if (otherDirs) {
+ otherDirs.forEach(function (oDir) {
+ console.log('rmdir %s', oDir);
+ });
+ }
+ })
+ .parse(process.argv);
+```
+
+ An `Array` is used for the value of a variadic argument. This applies to `program.args` as well as the argument passed
+ to your action as demonstrated above.
+
## Automated --help
The help information is auto-generated based on the information commander already knows about your program, so the following `--help` info is for free:
@@ -178,7 +209,7 @@ Examples:
- [more progress bars](https://github.com/substack/node-multimeter)
- [examples](https://github.com/visionmedia/commander.js/tree/master/examples)
-## License
+## License
(The MIT License)
diff --git a/index.js b/index.js
index baa3554d6..b70000684 100644
--- a/index.js
+++ b/index.js
@@ -127,6 +127,18 @@ Command.prototype.__proto__ = EventEmitter.prototype;
* });
*
* program
+ * .command('teardown [otherDirs...]')
+ * .description('run teardown commands')
+ * .action(function(dir, otherDirs) {
+ * console.log('dir "%s"', dir);
+ * if (otherDirs) {
+ * otherDirs.forEach(function (oDir) {
+ * console.log('dir "%s"', oDir);
+ * });
+ * }
+ * });
+ *
+ * program
* .command('*')
* .description('deploy the given env')
* .action(function(env) {
@@ -179,14 +191,30 @@ Command.prototype.parseExpectedArgs = function(args) {
if (!args.length) return;
var self = this;
args.forEach(function(arg) {
+ var argDetails = {
+ required: false,
+ name: '',
+ variadic: false
+ };
+
switch (arg[0]) {
case '<':
- self._args.push({ required: true, name: arg.slice(1, -1) });
+ argDetails.required = true;
+ argDetails.name = arg.slice(1, -1);
break;
case '[':
- self._args.push({ required: false, name: arg.slice(1, -1) });
+ argDetails.name = arg.slice(1, -1);
break;
}
+
+ if (argDetails.name.indexOf('...') === argDetails.name.length - 3) {
+ argDetails.variadic = true;
+ argDetails.name = argDetails.name.slice(0, -3);
+ }
+
+ if (argDetails.name) {
+ self._args.push(argDetails);
+ }
});
return this;
};
@@ -233,6 +261,12 @@ Command.prototype.action = function(fn) {
self._args.forEach(function(arg, i) {
if (arg.required && null == args[i]) {
self.missingArgument(arg.name);
+ } else if (arg.variadic) {
+ if (i !== self._args.length - 1) {
+ self.variadicArgNotLast(arg.name);
+ }
+
+ args[i] = args.slice(i);
}
});
@@ -663,6 +697,19 @@ Command.prototype.unknownOption = function(flag) {
process.exit(1);
};
+/**
+ * Variadic argument with `name` is not the last argument as required.
+ *
+ * @param {String} name
+ * @api private
+ */
+
+Command.prototype.variadicArgNotLast = function(name) {
+ console.error();
+ console.error(" error: variadic arguments must be last `%s'", name);
+ console.error();
+ process.exit(1);
+};
/**
* Set the program version to `str`.
@@ -726,9 +773,7 @@ Command.prototype.alias = function(alias) {
Command.prototype.usage = function(str) {
var args = this._args.map(function(arg) {
- return arg.required
- ? '<' + arg.name + '>'
- : '[' + arg.name + ']';
+ return humanReadableArgName(arg);
});
var usage = '[options]'
@@ -796,9 +841,7 @@ Command.prototype.commandHelp = function() {
var commands = this.commands.map(function(cmd) {
var args = cmd._args.map(function(arg) {
- return arg.required
- ? '<' + arg.name + '>'
- : '[' + arg.name + ']';
+ return humanReadableArgName(arg);
}).join(' ');
return [
@@ -920,3 +963,19 @@ function outputHelpIfNecessary(cmd, options) {
}
}
}
+
+/**
+ * Takes an argument an returns its human readable equivalent for help usage.
+ *
+ * @param {Object} arg
+ * @return {String}
+ * @api private
+ */
+
+function humanReadableArgName(arg) {
+ var nameOutput = arg.name + (arg.variadic === true ? '...' : '');
+
+ return arg.required
+ ? '<' + nameOutput + '>'
+ : '[' + nameOutput + ']'
+}
diff --git a/test/test.variadic.args.js b/test/test.variadic.args.js
new file mode 100644
index 000000000..2662128a3
--- /dev/null
+++ b/test/test.variadic.args.js
@@ -0,0 +1,61 @@
+/**
+ * Module dependencies.
+ */
+
+var program = require('../')
+ , should = require('should')
+ , util = require('util')
+ , programArgs = ['node', 'test', 'mycommand', 'arg0', 'arg1', 'arg2', 'arg3']
+ , requiredArg
+ , variadicArg;
+
+program
+ .version('0.0.1')
+ .command('mycommand [variadicArg...]')
+ .action(function (arg0, arg1) {
+ requiredArg = arg0;
+ variadicArg = arg1;
+ });
+
+program.parse(programArgs);
+
+requiredArg.should.eql('arg0');
+variadicArg.should.eql(['arg1', 'arg2', 'arg3']);
+
+program.args[0].should.eql('arg0');
+program.args[1].should.eql(['arg1', 'arg2', 'arg3']);
+
+program
+ .version('0.0.1')
+ .command('mycommand [optionalArg]')
+ .action(function (arg0, arg1) {
+
+ });
+
+// Make sure we still catch errors with required values for options
+var consoleErrors = [];
+var oldProcessExit = process.exit;
+var oldConsoleError = console.error;
+var errorMessage;
+
+process.exit = function() { throw new Error(consoleErrors.join('\n')); };
+console.error = function() {
+ consoleErrors.push(util.format.apply(util, arguments));
+};
+
+try {
+ program.parse(programArgs);
+
+ should.fail(null, null, 'An Error should had been thrown above');
+} catch (err) {
+ errorMessage = err.message;
+}
+
+process.exit = oldProcessExit;
+console.error = oldConsoleError;
+
+[
+ '',
+ ' error: variadic arguments must be last `variadicArg\'',
+ ''
+].join('\n').should.eql(errorMessage);