Skip to content

Commit

Permalink
createCommand factory routine (#1191)
Browse files Browse the repository at this point in the history
* Add factory method

* Fix return doc

* Can not use "this" for return type of createCommand

* Use return type of createCommand for subcommand

* Add mention of .command from .createCommand

* Remove trailing space

* Add examples for createCommand

* Explain example and make a little more realistic

* Add  comments pointing from .addCommand to .command

One of the downsides of extra ways of adding and creating commands is confusion with the more common way.

* Add createCommand to README

* Shift command/subcommand contrast

* Use single quotes in ts like in js, and clean up whitespace in new code
  • Loading branch information
shadowspawn committed Mar 2, 2020
1 parent 3c9f33f commit 8c3dd6f
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 3 deletions.
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

0 comments on commit 8c3dd6f

Please sign in to comment.