diff --git a/Readme.md b/Readme.md index efb96d3c4..8790fa9da 100644 --- a/Readme.md +++ b/Readme.md @@ -36,6 +36,7 @@ Read this in other languages: English | [简体中文](./Readme_zh-CN.md) - [.parse() and .parseAsync()](#parse-and-parseasync) - [Avoiding option name clashes](#avoiding-option-name-clashes) - [TypeScript](#typescript) + - [createCommand()](#createcommand) - [Node options such as `--harmony`](#node-options-such-as---harmony) - [Debugging stand-alone executable subcommands](#debugging-stand-alone-executable-subcommands) - [Override exit handling](#override-exit-handling) @@ -630,6 +631,19 @@ If you use `ts-node` and stand-alone executable subcommands written as `.ts` fi node -r ts-node/register pm.ts ``` +### createCommand() + +This factory function creates a new command. It is exported and may be used instead of using `new`, like: + +```js +const { createCommand } = require('commander'); +const program = createCommand(); +``` + +`createCommand` is also a method of the Command object, and creates a new command rather than a subcommand. This gets used internally +when creating subcommands using `.command()`, and you may override it to +customise the new subcommand (examples using [subclass](./examples/custom-command-class.js) and [function](./examples/custom-command-function.js)). + ### Node options such as `--harmony` You can enable `--harmony` option in two ways: diff --git a/examples/custom-command-class.js b/examples/custom-command-class.js new file mode 100644 index 000000000..cda3d06ac --- /dev/null +++ b/examples/custom-command-class.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +// const commander = require('commander'); // (normal include) +const commander = require('../'); // include commander in git clone of commander repo + +// Use a class override of createCommand to customise subcommands, +// in this example by adding --debug option. + +class MyCommand extends commander.Command { + createCommand(name) { + const cmd = new MyCommand(name); + cmd.option('-d,--debug', 'output options'); + return cmd; + } +}; + +const program = new MyCommand(); +program + .command('serve') + .option('--port ', 'specify port number', 80) + .action((cmd) => { + if (cmd.debug) { + console.log('Options:'); + console.log(cmd.opts()); + console.log(); + } + + console.log(`Start serve on port ${cmd.port}`); + }); + +program.parse(); + +// Try the following: +// node custom-command-class.js help serve +// node custom-command-class.js serve --debug +// node custom-command-class.js serve --debug --port 8080 diff --git a/examples/custom-command-function.js b/examples/custom-command-function.js new file mode 100644 index 000000000..f4d58c0e5 --- /dev/null +++ b/examples/custom-command-function.js @@ -0,0 +1,36 @@ +#!/usr/bin/env node + +// const commander = require('commander'); // (normal include) +const commander = require('../'); // include commander in git clone of commander repo + +// Override createCommand directly to customise subcommands, +// in this example by adding --debug option. + +const program = commander.createCommand(); + +// Customise subcommand creation +program.createCommand = (name) => { + const cmd = commander.createCommand(name); + cmd.option('-d,--debug', 'output options'); + return cmd; +}; + +program + .command('serve') + .option('--port ', 'specify port number', 80) + .action((cmd) => { + if (cmd.debug) { + console.log('Options:'); + console.log(cmd.opts()); + console.log(); + } + + console.log(`Start serve on port ${cmd.port}`); + }); + +program.parse(); + +// Try the following: +// node custom-command-function.js help serve +// node custom-command-function.js serve --debug +// node custom-command-function.js serve --debug --port 8080 diff --git a/index.js b/index.js index 818207dec..7de8db5f6 100644 --- a/index.js +++ b/index.js @@ -166,7 +166,7 @@ class Command extends EventEmitter { } opts = opts || {}; const args = nameAndArgs.split(/ +/); - const cmd = new Command(args.shift()); + const cmd = this.createCommand(args.shift()); if (desc) { cmd.description(desc); @@ -195,9 +195,26 @@ class Command extends EventEmitter { return cmd; }; + /** + * Factory routine to create a new unattached command. + * + * See .command() for creating an attached subcommand, which uses this routine to + * create the command. You can override createCommand to customise subcommands. + * + * @param {string} [name] + * @return {Command} new command + * @api public + */ + + createCommand(name) { + return new Command(name); + }; + /** * Add a prepared subcommand. * + * See .command() for creating an attached subcommand which inherits settings from its parent. + * * @param {Command} cmd - new subcommand * @return {Command} parent command for chaining * @api public diff --git a/tests/createCommand.test.js b/tests/createCommand.test.js new file mode 100644 index 000000000..e0450d7b2 --- /dev/null +++ b/tests/createCommand.test.js @@ -0,0 +1,36 @@ +const commander = require('../'); + +test('when createCommand then unattached', () => { + const program = new commander.Command(); + const cmd = program.createCommand(); + expect(program.commands.length).toBe(0); + expect(cmd.parent).toBeFalsy(); // (actually null, but use weaker test for unattached) +}); + +test('when subclass overrides createCommand then subcommand is subclass', () => { + class MyClass extends commander.Command { + constructor(name) { + super(); + this.myProperty = 'myClass'; + }; + + createCommand(name) { + return new MyClass(name); + }; + }; + const program = new MyClass(); + const sub = program.command('sub'); + expect(sub.myProperty).toEqual('myClass'); +}); + +test('when override createCommand then subcommand is custom', () => { + function createCustom(name) { + const cmd = new commander.Command(); + cmd.myProperty = 'custom'; + return cmd; + } + const program = createCustom(); + program.createCommand = createCustom; + const sub = program.command('sub'); + expect(sub.myProperty).toEqual('custom'); +}); diff --git a/typings/commander-tests.ts b/typings/commander-tests.ts index 568c6fa7a..0dc607137 100644 --- a/typings/commander-tests.ts +++ b/typings/commander-tests.ts @@ -185,3 +185,19 @@ const helpOptionThis3: commander.Command = program.helpOption(undefined, 'custom // on const onThis: commander.Command = program.on('--help', () => { }) + +// createCommand + +const createInstance1: commander.Command = program.createCommand(); +const createInstance2: commander.Command = program.createCommand('name'); + +class MyCommand extends commander.Command { + createCommand(name?: string) { + return new MyCommand(name); + } + myFunction() {} +} +const myProgram = new MyCommand(); +myProgram.myFunction(); +const mySub = myProgram.command('sub'); +mySub.myFunction(); diff --git a/typings/index.d.ts b/typings/index.d.ts index ffd7e8c97..9b4eadff3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -24,7 +24,7 @@ declare namespace commander { type OptionConstructor = { new (flags: string, description?: string): Option }; interface ParseOptions { - from: "node" | "electron" | "user"; + from: 'node' | 'electron' | 'user'; } interface Command { @@ -64,7 +64,7 @@ declare namespace commander { * @param opts - configuration options * @returns new command */ - command(nameAndArgs: string, opts?: CommandOptions): Command; + command(nameAndArgs: string, opts?: CommandOptions): ReturnType; /** * Define a command, implemented in a separate executable file. * @@ -85,9 +85,19 @@ declare namespace commander { */ command(nameAndArgs: string, description: string, opts?: commander.CommandOptions): this; + /** + * Factory routine to create a new unattached command. + * + * See .command() for creating an attached subcommand, which uses this routine to + * create the command. You can override createCommand to customise subcommands. + */ + createCommand(name?: string): Command; + /** * Add a prepared subcommand. * + * See .command() for creating an attached subcommand which inherits settings from its parent. + * * @returns parent command for chaining */ addCommand(cmd: Command): this;