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

Add support for getting merged options including globals #1671

Merged
merged 7 commits into from Jan 11, 2022
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 6 additions & 2 deletions Readme.md
Expand Up @@ -174,8 +174,6 @@ const program = new Command();
Options are defined with the `.option()` method, also serving as documentation for the options. Each option can have a short flag (single character) and a long name, separated by a comma or space or vertical bar ('|').

The parsed options can be accessed by calling `.opts()` on a `Command` object, and are passed to the action handler.
(You can also use `.getOptionValue()` and `.setOptionValue()` to work with a single option value,
and `.getOptionValueSource()` and `.setOptionValueWithSource()` when it matters where the option value came from.)

Multi-word options such as "--template-engine" are camel-cased, becoming `program.opts().templateEngine` etc.

Expand All @@ -186,6 +184,12 @@ You can use `--` to indicate the end of the options, and any remaining arguments

By default options on the command line are not positional, and can be specified before or after other arguments.

There are additional related routines for when `.opts()` is not enough:

- `.optsWithGlobals()` returns merged local and global option values
- `.getOptionValue()` and `.setOptionValue()` work with a single option value
- `.getOptionValueSource()` and `.setOptionValueWithSource()` include where the option value came from

### Common option types, boolean and value

The two most used option types are a boolean option, and an option which takes its value
Expand Down
24 changes: 24 additions & 0 deletions examples/optsWithGlobals.js
@@ -0,0 +1,24 @@
// const { Command } = require('commander'); // (normal include)
const { Command } = require('../'); // include commander in git clone of commander repo

// Show use of .optsWithGlobals(), and compare with .opts().

const program = new Command();

program
.option('-g, --global');

program
.command('sub')
.option('-l, --local')
.action((options, cmd) => {
console.log({
opts: cmd.opts(),
optsWithGlobals: cmd.optsWithGlobals()
});
});

program.parse();

// Try the following:
// node optsWithGlobals.js --global sub --local
17 changes: 15 additions & 2 deletions lib/command.js
Expand Up @@ -1441,11 +1441,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
}

/**
* Return an object containing options as key-value pairs
* Return an object containing local option values as key-value pairs.
*
* @return {Object}
*/
opts() {
opts(mergeOptions) {
shadowspawn marked this conversation as resolved.
Show resolved Hide resolved
if (this._storeOptionsAsProperties) {
// Preserve original behaviour so backwards compatible when still using properties
const result = {};
Expand All @@ -1461,6 +1461,19 @@ Expecting one of '${allowedValues.join("', '")}'`);
return this._optionValues;
}

/**
* Return an object containing merged local and global option values as key-value pairs.
*
* @return {Object}
*/
optsWithGlobals(mergeOptions) {
// globals overwrite locals
return getCommandAndParents(this).reduce(
(combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()),
{}
);
}

/**
* Internal bottleneck for handling of parsing errors.
*
Expand Down
63 changes: 63 additions & 0 deletions tests/options.optsWithGlobals.test.js
@@ -0,0 +1,63 @@
const commander = require('../');

test('when variety of options used with program then opts is same as optsWithGlobals', () => {
const program = new commander.Command();
program
.option('-b, --boolean')
.option('-r, --require-value <value)')
.option('-f, --float <value>', 'description', parseFloat)
.option('-d, --default-value <value)', 'description', 'default value')
.option('-n, --no-something');

program.parse(['-b', '-r', 'req', '-f', '1e2'], { from: 'user' });
expect(program.opts()).toEqual(program.optsWithGlobals());
});

test('when options in sub and program then optsWithGlobals includes both', () => {
const program = new commander.Command();
let mergedOptions;
program
.option('-g, --global <value>');
program
.command('sub')
.option('-l, --local <value)')
.action((options, cmd) => {
mergedOptions = cmd.optsWithGlobals();
});

program.parse(['-g', 'GGG', 'sub', '-l', 'LLL'], { from: 'user' });
expect(mergedOptions).toEqual({ global: 'GGG', local: 'LLL' });
});

test('when options in sub and subsub then optsWithGlobals includes both', () => {
const program = new commander.Command();
let mergedOptions;
program
.command('sub')
.option('-g, --global <value)')
.command('subsub')
.option('-l, --local <value)')
.action((options, cmd) => {
mergedOptions = cmd.optsWithGlobals();
});

program.parse(['sub', '-g', 'GGG', 'subsub', '-l', 'LLL'], { from: 'user' });
expect(mergedOptions).toEqual({ global: 'GGG', local: 'LLL' });
});

test('when same named option in sub and program then optsWithGlobals includes global', () => {
const program = new commander.Command();
let mergedOptions;
program
.option('-c, --common <value>')
.enablePositionalOptions();
program
.command('sub')
.option('-c, --common <value)')
.action((options, cmd) => {
mergedOptions = cmd.optsWithGlobals();
});

program.parse(['-c', 'GGG', 'sub', '-c', 'LLL'], { from: 'user' });
expect(mergedOptions).toEqual({ common: 'GGG' });
});
7 changes: 6 additions & 1 deletion typings/index.d.ts
Expand Up @@ -659,10 +659,15 @@ export class Command {
parseOptions(argv: string[]): ParseOptionsResult;

/**
* Return an object containing options as key-value pairs
* Return an object containing local option values as key-value pairs
*/
opts<T extends OptionValues>(): T;

/**
* Return an object containing merged local and global option values as key-value pairs.
*/
optsWithGlobals<T extends OptionValues>(): T;

/**
* Set the description.
*
Expand Down
12 changes: 12 additions & 0 deletions typings/index.test-d.ts
Expand Up @@ -216,6 +216,18 @@ expectType<string>(myCheeseOption.cheese);
// @ts-expect-error Check that options strongly typed and does not allow arbitrary properties
expectType(myCheeseOption.foo);

// optsWithGlobals
const optsWithGlobals = program.optsWithGlobals();
expectType<commander.OptionValues>(optsWithGlobals);
expectType(optsWithGlobals.foo);
expectType(optsWithGlobals.bar);

// optsWithGlobals with generics
const myCheeseOptionWithGlobals = program.optsWithGlobals<MyCheeseOption>();
expectType<string>(myCheeseOptionWithGlobals.cheese);
// @ts-expect-error Check that options strongly typed and does not allow arbitrary properties
expectType(myCheeseOptionWithGlobals.foo);

// description
expectType<commander.Command>(program.description('my description'));
expectType<string>(program.description());
Expand Down