diff --git a/index.js b/index.js index 7022b73fe..934275ef3 100644 --- a/index.js +++ b/index.js @@ -193,11 +193,24 @@ Command.prototype.command = function(nameAndArgs, actionOptsOrExecDesc, execOpts this.commands.push(cmd); cmd.parseExpectedArgs(args); cmd.parent = this; + if (!desc) cmd._addCommandListener(); // Add listener to handle nested subcommand if (desc) return this; return cmd; }; +/** + * @api public + */ +Command.prototype.addCommand = function(cmd) { + if (!cmd._name) throw Error('addCommand name is not specified for command'); + + this.commands.push(cmd); + cmd.parent = this; + cmd._addCommandListener(); + return this; +}; + /** * Define argument syntax for the top-level command. * @@ -301,6 +314,67 @@ Command.prototype._exit = function(exitCode, code, message) { process.exit(exitCode); }; +/** + * @api private + */ + +Command.prototype._addCommandListener = function() { + var self = this; + var listener = function(args, unknown) { + // Parse any so-far unknown options + args = args || []; + unknown = unknown || []; + + var parsed = self.parseOptions(unknown); + + // Leftover arguments need to be pushed back. Fixes issue #56 + if (parsed.args.length) args = parsed.args.concat(args); + + if (args.length && self.listenerCount(`command:${args[0]}`)) { + self.parseArgs(args, parsed.unknown); + } else { + // Output help if necessary + outputHelpIfNecessary(self, parsed.unknown); + self._checkForMissingMandatoryOptions(); + + // 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 && args[i] == null) { + self.missingArgument(arg.name); + } else if (arg.variadic) { + if (i !== self._args.length - 1) { + self.variadicArgNotLast(arg.name); + } + + args[i] = args.splice(i); + } + }); + + self.emit('action', args); + } + }; + + // Add listener to parent, or program. + let eventEmitter = this.parent; + let eventName = `command:${this._name}`; + if (!eventEmitter) { + eventEmitter = this; // program + eventName = 'program-command'; + } + // Avoid adding listener twice (currently adding on demand from multiple places). + if (eventEmitter.listenerCount(eventName) === 0) { + eventEmitter.on(eventName, listener); + } + const aliasEventName = `command:${this._alias}`; + if (this._alias && eventEmitter.listenerCount(aliasEventName) === 0) { + eventEmitter.on(aliasEventName, listener); + } +}; + /** * Register callback `fn` for the command. * @@ -320,39 +394,9 @@ Command.prototype._exit = function(exitCode, code, message) { Command.prototype.action = function(fn) { var self = this; - var listener = function(args, unknown) { - // Parse any so-far unknown options - args = args || []; - unknown = unknown || []; - - var parsed = self.parseOptions(unknown); - - // Output help if necessary - outputHelpIfNecessary(self, parsed.unknown); - self._checkForMissingMandatoryOptions(); - - // 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]); - } - - // 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 && args[i] == null) { - self.missingArgument(arg.name); - } else if (arg.variadic) { - if (i !== self._args.length - 1) { - self.variadicArgNotLast(arg.name); - } - - args[i] = args.splice(i); - } - }); + this._addCommandListener(); + var listener = function(args) { // The .action callback takes an extra parameter which is the command itself. var expectedArgsCount = self._args.length; var actionArgs = args.slice(0, expectedArgsCount); @@ -368,14 +412,10 @@ Command.prototype.action = function(fn) { fn.apply(self, actionArgs); }; - var parent = this.parent || this; - if (parent === this) { - parent.on('program-command', listener); - } else { - parent.on('command:' + this._name, listener); - } - if (this._alias) parent.on('command:' + this._alias, listener); + this.on('action', listener); + if (this._alias) this.on('action', listener); + return this; }; @@ -874,8 +914,10 @@ Command.prototype.parseArgs = function(args, unknown) { if (this.listeners('command:' + name).length) { this.emit('command:' + args.shift(), args, unknown); } else { - this.emit('program-command', args, unknown); this.emit('command:*', args, unknown); + if (!this.parent) { + this.emit('program-command', args, unknown); + } } } else { outputHelpIfNecessary(this, unknown); @@ -885,7 +927,7 @@ Command.prototype.parseArgs = function(args, unknown) { this.unknownOption(unknown[0]); } // Call the program action handler, unless it has a (missing) required parameter and signature does not match. - if (this._args.filter(function(a) { return a.required; }).length === 0) { + if (!this.parent && this._args.filter(function(a) { return a.required; }).length === 0) { this.emit('program-command'); } }