From 4788f703fea0d04535df3b3ca66e60ab82777590 Mon Sep 17 00:00:00 2001 From: Itay Neeman Date: Tue, 22 Nov 2011 23:42:16 -0800 Subject: [PATCH] Add help for subcommands (should resolve issue #2) --- examples/deploy | 5 +++ lib/commander.js | 71 ++++++++++++++++++++++++++--------- test/test.options.commands.js | 16 +++++++- 3 files changed, 72 insertions(+), 20 deletions(-) diff --git a/examples/deploy b/examples/deploy index b540267fc..95e4a2a46 100755 --- a/examples/deploy +++ b/examples/deploy @@ -28,6 +28,11 @@ program .option("-e, --exec_mode ", "Which exec mode to use") .action(function(cmd, options){ console.log('exec "%s" using %s mode', cmd, options.exec_mode); + }).on('--help', function() { + console.log(' Examples:'); + console.log(''); + console.log(' $ deploy exec sequential'); + console.log(' $ deploy exec async'); }); program diff --git a/lib/commander.js b/lib/commander.js index 0ce37b7d0..24f1f649a 100644 --- a/lib/commander.js +++ b/lib/commander.js @@ -201,11 +201,16 @@ Command.prototype.action = function(fn){ var self = this; this.parent.on(this.name, function(args, unknown){ // Parse any so-far unknown options + unknown = unknown || []; var parsed = self.parseOptions(unknown); + // Output help if necessary + outputHelpIfNecessary(self, parsed.unknown); + // If there are still any unknown options, then we simply - // die. - if (parsed.unknown.length > 0) { + // 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]); } @@ -218,7 +223,12 @@ Command.prototype.action = function(fn){ // 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; + if (self.args.length) { + args[self.args.length] = self; + } + else { + args.push(self); + } fn.apply(this, args); }); @@ -388,14 +398,16 @@ Command.prototype.parseArgs = function(args, unknown){ name = args[0]; if (this.listeners(name).length) { this.emit(args.shift(), args, unknown); - } else { + } else { this.emit('*', args); } } else { + outputHelpIfNecessary(this, unknown); + // If there were no args and we have unknown options, // then they are extraneous and we need to error. - if (unknown.length > 0) { + if (unknown.length > 0) { this.unknownOption(unknown[0]); } } @@ -593,9 +605,15 @@ Command.prototype.description = function(str){ */ Command.prototype.usage = function(str){ + var args = this.args.map(function(arg){ + return arg.required + ? '<' + arg.name + '>' + : '[' + arg.name + ']'; + }); var usage = '[options' + (this.commands.length ? '] [command' : '') - + ']'; + + ']' + + (this.args.length ? ' ' + args : ''); if (0 == arguments.length) return this._usage || usage; this._usage = str; return this; @@ -623,10 +641,14 @@ Command.prototype.largestOptionLength = function(){ Command.prototype.optionHelp = function(){ var width = this.largestOptionLength(); - return this.options.map(function(option){ - return pad(option.flags, width) - + ' ' + option.description; - }).join('\n'); + + // Prepend the help information + return [pad('-h, --help', width) + ' ' + 'output usage information'] + .concat(this.options.map(function(option){ + return pad(option.flags, width) + + ' ' + option.description; + })) + .join('\n'); }; /** @@ -648,7 +670,10 @@ Command.prototype.commandHelp = function(){ ? '<' + arg.name + '>' : '[' + arg.name + ']'; }).join(' '); - return cmd.name + ' ' + args + return cmd.name + + (cmd.options.length + ? ' [options]' + : '') + ' ' + args + (cmd.description() ? '\n' + cmd.description() : ''); @@ -944,12 +969,22 @@ function pad(str, width) { } /** - * Default -h, --help option. + * Output help information if necessary + * + * @param {Command} command to output help for + * @param {Array} array of options to search for -h or --help + * @api private */ -exports.option('-h, --help', 'output usage information'); -exports.on('help', function(){ - exports.emit('--help'); - process.stdout.write(this.helpInformation()); - process.exit(0); -}); +function outputHelpIfNecessary(cmd, options) { + options = options || []; + + for(var i = 0; i < options.length; i++) { + if (options[i] === '--help' || options[i] === '-h') { + cmd.emit('--help'); + process.stdout.write(cmd.helpInformation()); + process.exit(0); + } + } + +} diff --git a/test/test.options.commands.js b/test/test.options.commands.js index 81401a502..d4460ec0a 100644 --- a/test/test.options.commands.js +++ b/test/test.options.commands.js @@ -13,6 +13,7 @@ program var envValue = ""; var cmdValue = ""; +var customHelp = false; program .command('setup [env]') @@ -33,6 +34,8 @@ program .option("-t, --target [target]", "Target to use") .action(function(cmd, options){ cmdValue = cmd; + }).on("--help", function() { + customHelp = true; }); program @@ -85,7 +88,15 @@ cmdValue.should.equal("exec3"); // 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', '--config', 'conf6', 'exec', '--help']); +} catch(ex) { + program.config.should.equal("conf6"); +} try { program.parse(['node', 'test', '--config', 'conf', 'exec', '-t', 'target1', 'exec1', '-e']); @@ -93,6 +104,7 @@ try { catch(ex) { } -exceptionOccurred.should.be.true; +process.exit = oldProcessExit; -process.exit = oldProcessExit; \ No newline at end of file +exceptionOccurred.should.be.true; +customHelp.should.be.true;