diff --git a/examples/use-subcommand.js b/examples/use-subcommand.js new file mode 100755 index 000000000..90af11bfb --- /dev/null +++ b/examples/use-subcommand.js @@ -0,0 +1,58 @@ +#!/usr/bin/env node + +// This is an example of useSubcommand +// +// $ use-subcommand journal list myjounal +// $ use-subcommand journal delete myjounal +// +// With options +// $ use-subcommand journal -q delete -f myjounal + +// const { Command } = require('commander'); +const { Command } = require('..'); + +function importSubCommand() { + const journalCmd = new Command() + .name('journal') + .description('Journal utils'); + + journalCmd + .command('list ') + .description('List journal') + .action((path, cmdInstance) => { + console.log('List journal'); + console.log('Path is', path); + console.log('Quiet =', Boolean(cmdInstance.parent.quiet)); + }); + + journalCmd + .command('delete ') + .description('Delete journal') + .option('-f, --force') + .action((path, cmdInstance) => { + console.log('List journal'); + console.log('Path is', path); + console.log('Quiet =', Boolean(cmdInstance.parent.quiet)); + console.log('Force =', Boolean(cmdInstance.force)); + }); + + return journalCmd; +} + +// this is supposedly a module, so in real case this would be `require` +const journalSubCommand = importSubCommand(); + +const program = new Command(); +program + .option('-q, --quiet') + .useSubcommand(journalSubCommand); + +program + .command('hello ') + .description('Greeting') + .action((name, cmdInstance) => { + console.log(`Hello ${name}!`); + }); + +if (process.argv.length <= 2) program.help(); +program.parse(process.argv); diff --git a/index.js b/index.js index 4b83c97fb..bda4d1395 100644 --- a/index.js +++ b/index.js @@ -1210,6 +1210,46 @@ Command.prototype.help = function(cb) { process.exit(); }; +/** + * Add action-like sub command + * command name is taken from name() property - must be defined + * + * @returns {Command} `this` instance + */ + +Command.prototype.useSubcommand = function(subCommand) { + if (this._args.length > 0) throw Error('useSubcommand cannot be applied to a command with explicit args'); + if (!subCommand._name) throw Error('subCommand name is not specified'); + + var self = subCommand; + var listener = function(args, unknown) { + // Parse any so-far unknown options + args = args || []; + unknown = unknown || []; + + var parsed = self.parseOptions(unknown); + if (parsed.args.length) args = parsed.args.concat(args); + unknown = parsed.unknown; + + // Output help if necessary + const helpRequested = unknown.includes('--help') || unknown.includes('-h'); + const noFutherCommands = !args || !self.listeners('command:' + args[0]); + if (helpRequested && noFutherCommands) { + self.outputHelp(); + process.exit(0); + } + + self.parseArgs(args, unknown); + }; + + for (const label of [subCommand._name, subCommand._alias]) { + if (label) this.on('command:' + label, listener); + } + this.commands.push(subCommand); + subCommand.parent = this; + return this; +}; + /** * Creates an instance of sub command * diff --git a/typings/index.d.ts b/typings/index.d.ts index 8a751a30c..0ac774147 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -204,6 +204,15 @@ declare namespace local { forwardSubcommands(): Command; + /** + * Add action-like sub command + * command name is taken from name() property - must be defined + * + * @returns {Command} `this` instance + */ + + useSubcommand(subCommand : Command): Command; + /** * Returns an object with all options values, including parent options values * This makes it especially useful with forwardSubcommands as it collects