Skip to content

Commit

Permalink
Add options for subcommands
Browse files Browse the repository at this point in the history
  • Loading branch information
itay committed Nov 20, 2011
1 parent 41fdf89 commit b32fcca
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 16 deletions.
13 changes: 7 additions & 6 deletions examples/deploy
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,19 @@ program
program
.command('setup [env]')
.description('run setup commands for all envs')
.action(function(env){
.option("-s, --setup_mode [mode]", "Which setup mode to use")
.action(function(env, options){
var mode = options.setup_mode || "normal";
env = env || 'all';
console.log('setup for %s env(s)', env);
console.log('setup for %s env(s) with %s mode', env, mode);
});

program
.command('exec <cmd>')
.description('execute the given remote cmd')
.action(function(cmd){
console.log('exec "%s"', cmd);
.option("-e, --exec_mode <mode>", "Which exec mode to use")
.action(function(cmd, options){
console.log('exec "%s" using %s mode', cmd, options.exec_mode);
});

program
Expand All @@ -34,5 +37,3 @@ program
});

program.parse(process.argv);


52 changes: 42 additions & 10 deletions lib/commander.js
Original file line number Diff line number Diff line change
Expand Up @@ -199,12 +199,27 @@ Command.prototype.parseExpectedArgs = function(args){

Command.prototype.action = function(fn){
var self = this;
this.parent.on(this.name, function(args){
this.parent.on(this.name, function(args, unknown){
// Parse any so-far unknown options
var parsed = self.parseOptions(unknown);

// If there are still any unknown options, then we simply
// die.
if (parsed.unknown.length > 0) {
self.unknownOption(parsed.unknown[0]);
}

self.args.forEach(function(arg, i){
if (arg.required && null == args[i]) {
self.missingArgument(arg.name);
}
});

// Always append ourselves to the end of the arguments,
// to make sure we match the number of arguments the user
// expects
args[self.args.length] = self;

fn.apply(this, args);
});
return this;
Expand Down Expand Up @@ -320,8 +335,9 @@ Command.prototype.parse = function(argv){
if (!this.name) this.name = basename(argv[1]);

// process argv
this.args = this.parseOptions(this.normalize(argv));
return this.parseArgs(this.args);
var parsed = this.parseOptions(this.normalize(argv.slice(2)));
this.args = parsed.args;
return this.parseArgs(this.args, parsed.unknown);
};

/**
Expand Down Expand Up @@ -363,19 +379,26 @@ Command.prototype.normalize = function(args){
* @api private
*/

Command.prototype.parseArgs = function(args){
Command.prototype.parseArgs = function(args, unknown){
var cmds = this.commands
, len = cmds.length
, name;

if (args.length) {
name = args[0];
if (this.listeners(name).length) {
this.emit(args.shift(), args);
} else {
this.emit(args.shift(), args, unknown);
} else {
this.emit('*', args);
}
}
else {
// If there were no args and we have unknown options,
// then they are extraneous and we need to error.
if (unknown.length > 0) {
this.unknownOption(unknown[0]);
}
}

return this;
};
Expand Down Expand Up @@ -407,12 +430,13 @@ Command.prototype.optionFor = function(arg){

Command.prototype.parseOptions = function(argv){
var args = []
, argv = argv.slice(2)
, len = argv.length
, literal
, option
, arg;

var unknownOptions = [];

// parse options
for (var i = 0; i < len; ++i) {
arg = argv[i];
Expand Down Expand Up @@ -457,14 +481,22 @@ Command.prototype.parseOptions = function(argv){

// looks like an option
if (arg.length > 1 && '-' == arg[0]) {
this.unknownOption(arg);
unknownOptions.push(arg);

// If the next argument looks like it might be
// an argument for this option, we pass it on.
// If it isn't, then it'll simply be ignored
if (argv[i+1] && !(argv[i+1][0] === '-')) {
unknownOptions.push(argv[++i]);
}
continue;
}

// arg
args.push(arg);
}

return args;
return {args: args, unknown: unknownOptions};
};

/**
Expand Down
87 changes: 87 additions & 0 deletions test/test.options.commands.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/**
* Module dependencies.
*/

var program = require('../')
, should = require('should');

program
.version('0.0.1')
.option('-C, --chdir <path>', 'change the working directory')
.option('-c, --config <path>', 'set config path. defaults to ./deploy.conf')
.option('-T, --no-tests', 'ignore test hook')

program
.command('setup [env]')
.description('run setup commands for all envs')
.option("-s, --setup_mode [mode]", "Which setup mode to use")
.option("-o, --host [host]", "Host to use")
.action(function(env, options){
var mode = options.setup_mode || "normal";
env = env || 'all';

env.should.equal('env1');
});

program
.command('exec <cmd>')
.description('execute the given remote cmd')
.option("-e, --exec_mode <mode>", "Which exec mode to use")
.option("-t, --target [target]", "Target to use")
.action(function(cmd, options){
cmd.should.equal('exec1');
});

program
.command('*')
.action(function(env){
console.log('deploying "%s"', env);
});

program.parse(['node', 'test', '--config', 'conf']);
program.config.should.equal("conf");
program.commands[0].should.not.have.property.setup_mode;
program.commands[1].should.not.have.property.exec_mode;

program.parse(['node', 'test', '--config', 'conf', 'setup', '--setup_mode', 'mode3', 'env1']);
program.config.should.equal("conf");
program.commands[0].setup_mode.should.equal("mode3");
program.commands[0].should.not.have.property.host;

program.parse(['node', 'test', '--config', 'conf', 'setup', '--setup_mode', 'mode3', '-o', 'host1', 'env1']);
program.config.should.equal("conf");
program.commands[0].setup_mode.should.equal("mode3");
program.commands[0].host.should.equal("host1");

program.parse(['node', 'test', '--config', 'conf', 'setup', '-s', 'mode4', 'env1']);
program.config.should.equal("conf");
program.commands[0].setup_mode.should.equal("mode4");

program.parse(['node', 'test', '--config', 'conf', 'exec', '--exec_mode', 'mode1', 'exec1']);
program.config.should.equal("conf");
program.commands[1].exec_mode.should.equal("mode1");
program.commands[1].should.not.have.property.target;

program.parse(['node', 'test', '--config', 'conf', 'exec', '-e', 'mode2', 'exec1']);
program.config.should.equal("conf");
program.commands[1].exec_mode.should.equal("mode2");

program.parse(['node', 'test', '--config', 'conf', 'exec', '--target', 'target1', '-e', 'mode2', 'exec1']);
program.config.should.equal("conf");
program.commands[1].exec_mode.should.equal("mode2");
program.commands[1].target.should.equal("target1");

// Make sure we still catch errors with required values for options
var exceptionOccurred = false;
var oldProcessExit = process.exit;
process.exit = function() { exceptionOccurred = true; throw new Error(); };

try {
program.parse(['node', 'test', '--config', 'conf', 'exec', '-t', 'target1', 'exec1', '-e']);
}
catch(ex) {
}

exceptionOccurred.should.be.true;

process.exit = oldProcessExit;

0 comments on commit b32fcca

Please sign in to comment.