Skip to content

Commit

Permalink
Completed nested command support.
Browse files Browse the repository at this point in the history
Nested command support was almost completely available, however, a few things
seemed to be missing to me.

* `this` context on `action` event listeners was not bound properly.
* We output `help` too early, the deepest referenced command should handle help.
  • Loading branch information
nowells committed Aug 27, 2014
1 parent 1333365 commit c05a6c4
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 24 deletions.
55 changes: 31 additions & 24 deletions index.js
Expand Up @@ -215,37 +215,44 @@ Command.prototype.action = function(fn){
args = args || [];
unknown = unknown || [];

var parsed = self.parseOptions(unknown);

// Output help if necessary
outputHelpIfNecessary(self, parsed.unknown);
var parsed = self.parseOptions(args.concat(unknown));

// If there are still any unknown options, then we simply
// die, unless someone asked for help, in which case we give it
// to them, and then we die.
if (parsed.unknown.length > 0) {
self.unknownOption(parsed.unknown[0]);
if (parsed.args.length && self.listeners(parsed.args[0]).length) {
var result = self.parseArgs(parsed.args, parsed.unknown);
}
else {
// If there are still any unknown options, then we simply
// die, unless someone asked for help, in which case we give it
// to them, and then we die.
if (parsed.unknown.length > 0) {
// Output help if necessary
outputHelpIfNecessary(self, parsed.unknown);

self.unknownOption(parsed.unknown[0]);
}

// Leftover arguments need to be pushed back. Fixes issue #56
if (parsed.args.length) args = parsed.args.concat(args);
self._args.forEach(function(arg, i){
if (arg.required && null == parsed.args[i]) {
// Output help if necessary
outputHelpIfNecessary(self, parsed.unknown);

self._args.forEach(function(arg, i){
if (arg.required && null == args[i]) {
self.missingArgument(arg.name);
}
});
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
if (self._args.length) {
args[self._args.length] = self;
} else {
args.push(self);
}

fn.apply(this, args);
// Always append ourselves to the end of the arguments,
// to make sure we match the number of arguments the user
// expects
if (self._args.length) {
parsed.args[self._args.length] = self;
} else {
parsed.args.push(self);
}

fn.apply(self, parsed.args);
}
};
this.parent.on(this._name, listener);
if (this._alias) this.parent.on(this._alias, listener);
Expand Down
21 changes: 21 additions & 0 deletions test/test.options.command-context.js
@@ -0,0 +1,21 @@
/**
* Module dependencies.
*/

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

program
.version('0.0.1');

var commandContext;
var optionContext;

var setup = program.command('setup')
.description('run setup commands for all envs')
.action(function(env, options){
commandContext = this;
});

program.parse(['node', 'test', 'setup']);
commandContext._name.should.equal('setup');
88 changes: 88 additions & 0 deletions test/test.options.nested-commands.js
@@ -0,0 +1,88 @@
/**
* Module dependencies.
*/

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

var commandContext;

program
.version('0.0.1')
.on('--help', function() {
helpCommand = this._name;
});

var setupCommand = program
.command('setup')
.action(function(env, options){
commandContext = this;
})
.on('--help', function() {
helpCommand = this._name;
});

var addCommand = setupCommand
.command('add')
.option('--value [value]')
.action(function() {
commandContext = this;
})
.on('--help', function() {
helpCommand = this._name;
});

var remoteCommand = addCommand
.command('remote')
.action(function() {
commandContext = this;
})
.on('--help', function() {
helpCommand = this._name;
});

program.parse(['node', 'test', 'setup']);
commandContext._name.should.equal('setup');

program.parse(['node', 'test', 'setup', 'add']);
commandContext._name.should.equal('add');

program.parse(['node', 'test', 'setup', 'add', '--value', 'true']);
commandContext._name.should.equal('add');

program.parse(['node', 'test', 'setup', 'add', 'remote']);
commandContext._name.should.equal('remote');

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

try {
program.parse(['node', 'test', '--help']);
} catch(ex) {
}
helpCommand.should.equal('test');

try {
program.parse(['node', 'test', 'setup', '--help']);
} catch(ex) {
}
helpCommand.should.equal('setup');

try {
program.parse(['node', 'test', 'setup', 'add', '--help']);
} catch(ex) {
}
helpCommand.should.equal('add');

try {
program.parse(['node', 'test', 'setup', 'add', 'remote', '--help']);
} catch(ex) {
}
helpCommand.should.equal('remote');

process.exit = oldProcessExit;
exceptionOccurred.should.be.true;

0 comments on commit c05a6c4

Please sign in to comment.