Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

createCommand factory routine #1191

Merged
merged 13 commits into from Mar 2, 2020
14 changes: 14 additions & 0 deletions Readme.md
Expand Up @@ -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)
Expand Down Expand Up @@ -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:
Expand Down
36 changes: 36 additions & 0 deletions 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 <port-number>', '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
36 changes: 36 additions & 0 deletions 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 <port-number>', '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
19 changes: 18 additions & 1 deletion index.js
Expand Up @@ -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);
Expand Down Expand Up @@ -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
Expand Down
36 changes: 36 additions & 0 deletions 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');
});
16 changes: 16 additions & 0 deletions typings/commander-tests.ts
Expand Up @@ -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();
14 changes: 12 additions & 2 deletions typings/index.d.ts
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<this['createCommand']>;
/**
* Define a command, implemented in a separate executable file.
*
Expand All @@ -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;
Expand Down