Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for variadic arguments #277

Merged
merged 1 commit into from
Oct 21, 2014
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 32 additions & 1 deletion Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <dir> [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:
Expand Down Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Heh - nice catch here

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It wasn't on purpose, thank Atom.io. I didn't even catch this when reviewing the diff. ;)


(The MIT License)

Expand Down
75 changes: 67 additions & 8 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,18 @@ Command.prototype.__proto__ = EventEmitter.prototype;
* });
*
* program
* .command('teardown <dir> [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) {
Expand Down Expand Up @@ -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;
};
Expand Down Expand Up @@ -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);
}
});

Expand Down Expand Up @@ -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`.
Expand Down Expand Up @@ -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]'
Expand Down Expand Up @@ -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 [
Expand Down Expand Up @@ -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) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments are required for the the function

var nameOutput = arg.name + (arg.variadic === true ? '...' : '');

return arg.required
? '<' + nameOutput + '>'
: '[' + nameOutput + ']'
}
61 changes: 61 additions & 0 deletions test/test.variadic.args.js
Original file line number Diff line number Diff line change
@@ -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 <requiredArg> [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 <variadicArg...> [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);