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);