Skip to content

Commit 772eb53

Browse files
authored
Add support for getting merged options including globals (#1671)
* Proof of concept * Use separate method for optsWithGlobals * Add documentation * Add tests * Simplify description * Add example * Remove unused param
1 parent f902f6d commit 772eb53

File tree

6 files changed

+125
-4
lines changed

6 files changed

+125
-4
lines changed

Readme.md

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -174,8 +174,6 @@ const program = new Command();
174174
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 ('|').
175175

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

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

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

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

187+
There are additional related routines for when `.opts()` is not enough:
188+
189+
- `.optsWithGlobals()` returns merged local and global option values
190+
- `.getOptionValue()` and `.setOptionValue()` work with a single option value
191+
- `.getOptionValueSource()` and `.setOptionValueWithSource()` include where the option value came from
192+
189193
### Common option types, boolean and value
190194

191195
The two most used option types are a boolean option, and an option which takes its value

examples/optsWithGlobals.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
// const { Command } = require('commander'); // (normal include)
2+
const { Command } = require('../'); // include commander in git clone of commander repo
3+
4+
// Show use of .optsWithGlobals(), and compare with .opts().
5+
6+
const program = new Command();
7+
8+
program
9+
.option('-g, --global');
10+
11+
program
12+
.command('sub')
13+
.option('-l, --local')
14+
.action((options, cmd) => {
15+
console.log({
16+
opts: cmd.opts(),
17+
optsWithGlobals: cmd.optsWithGlobals()
18+
});
19+
});
20+
21+
program.parse();
22+
23+
// Try the following:
24+
// node optsWithGlobals.js --global sub --local

lib/command.js

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1441,7 +1441,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
14411441
}
14421442

14431443
/**
1444-
* Return an object containing options as key-value pairs
1444+
* Return an object containing local option values as key-value pairs.
14451445
*
14461446
* @return {Object}
14471447
*/
@@ -1461,6 +1461,19 @@ Expecting one of '${allowedValues.join("', '")}'`);
14611461
return this._optionValues;
14621462
}
14631463

1464+
/**
1465+
* Return an object containing merged local and global option values as key-value pairs.
1466+
*
1467+
* @return {Object}
1468+
*/
1469+
optsWithGlobals() {
1470+
// globals overwrite locals
1471+
return getCommandAndParents(this).reduce(
1472+
(combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()),
1473+
{}
1474+
);
1475+
}
1476+
14641477
/**
14651478
* Internal bottleneck for handling of parsing errors.
14661479
*
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
const commander = require('../');
2+
3+
test('when variety of options used with program then opts is same as optsWithGlobals', () => {
4+
const program = new commander.Command();
5+
program
6+
.option('-b, --boolean')
7+
.option('-r, --require-value <value)')
8+
.option('-f, --float <value>', 'description', parseFloat)
9+
.option('-d, --default-value <value)', 'description', 'default value')
10+
.option('-n, --no-something');
11+
12+
program.parse(['-b', '-r', 'req', '-f', '1e2'], { from: 'user' });
13+
expect(program.opts()).toEqual(program.optsWithGlobals());
14+
});
15+
16+
test('when options in sub and program then optsWithGlobals includes both', () => {
17+
const program = new commander.Command();
18+
let mergedOptions;
19+
program
20+
.option('-g, --global <value>');
21+
program
22+
.command('sub')
23+
.option('-l, --local <value)')
24+
.action((options, cmd) => {
25+
mergedOptions = cmd.optsWithGlobals();
26+
});
27+
28+
program.parse(['-g', 'GGG', 'sub', '-l', 'LLL'], { from: 'user' });
29+
expect(mergedOptions).toEqual({ global: 'GGG', local: 'LLL' });
30+
});
31+
32+
test('when options in sub and subsub then optsWithGlobals includes both', () => {
33+
const program = new commander.Command();
34+
let mergedOptions;
35+
program
36+
.command('sub')
37+
.option('-g, --global <value)')
38+
.command('subsub')
39+
.option('-l, --local <value)')
40+
.action((options, cmd) => {
41+
mergedOptions = cmd.optsWithGlobals();
42+
});
43+
44+
program.parse(['sub', '-g', 'GGG', 'subsub', '-l', 'LLL'], { from: 'user' });
45+
expect(mergedOptions).toEqual({ global: 'GGG', local: 'LLL' });
46+
});
47+
48+
test('when same named option in sub and program then optsWithGlobals includes global', () => {
49+
const program = new commander.Command();
50+
let mergedOptions;
51+
program
52+
.option('-c, --common <value>')
53+
.enablePositionalOptions();
54+
program
55+
.command('sub')
56+
.option('-c, --common <value)')
57+
.action((options, cmd) => {
58+
mergedOptions = cmd.optsWithGlobals();
59+
});
60+
61+
program.parse(['-c', 'GGG', 'sub', '-c', 'LLL'], { from: 'user' });
62+
expect(mergedOptions).toEqual({ common: 'GGG' });
63+
});

typings/index.d.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -659,10 +659,15 @@ export class Command {
659659
parseOptions(argv: string[]): ParseOptionsResult;
660660

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

666+
/**
667+
* Return an object containing merged local and global option values as key-value pairs.
668+
*/
669+
optsWithGlobals<T extends OptionValues>(): T;
670+
666671
/**
667672
* Set the description.
668673
*

typings/index.test-d.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,18 @@ expectType<string>(myCheeseOption.cheese);
216216
// @ts-expect-error Check that options strongly typed and does not allow arbitrary properties
217217
expectType(myCheeseOption.foo);
218218

219+
// optsWithGlobals
220+
const optsWithGlobals = program.optsWithGlobals();
221+
expectType<commander.OptionValues>(optsWithGlobals);
222+
expectType(optsWithGlobals.foo);
223+
expectType(optsWithGlobals.bar);
224+
225+
// optsWithGlobals with generics
226+
const myCheeseOptionWithGlobals = program.optsWithGlobals<MyCheeseOption>();
227+
expectType<string>(myCheeseOptionWithGlobals.cheese);
228+
// @ts-expect-error Check that options strongly typed and does not allow arbitrary properties
229+
expectType(myCheeseOptionWithGlobals.foo);
230+
219231
// description
220232
expectType<commander.Command>(program.description('my description'));
221233
expectType<string>(program.description());

0 commit comments

Comments
 (0)