diff --git a/Readme.md b/Readme.md index 2851b4e62..63c443c40 100644 --- a/Readme.md +++ b/Readme.md @@ -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. @@ -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 diff --git a/examples/optsWithGlobals.js b/examples/optsWithGlobals.js new file mode 100644 index 000000000..366597c06 --- /dev/null +++ b/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 diff --git a/lib/command.js b/lib/command.js index 12e7bb7be..ffd8a1d46 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1441,7 +1441,7 @@ 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} */ @@ -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() { + // globals overwrite locals + return getCommandAndParents(this).reduce( + (combinedOptions, cmd) => Object.assign(combinedOptions, cmd.opts()), + {} + ); + } + /** * Internal bottleneck for handling of parsing errors. * diff --git a/tests/options.optsWithGlobals.test.js b/tests/options.optsWithGlobals.test.js new file mode 100644 index 000000000..4cc52ecd8 --- /dev/null +++ b/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 ', 'description', parseFloat) + .option('-d, --default-value { + const program = new commander.Command(); + let mergedOptions; + program + .option('-g, --global '); + program + .command('sub') + .option('-l, --local { + 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 { + 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 ') + .enablePositionalOptions(); + program + .command('sub') + .option('-c, --common { + mergedOptions = cmd.optsWithGlobals(); + }); + + program.parse(['-c', 'GGG', 'sub', '-c', 'LLL'], { from: 'user' }); + expect(mergedOptions).toEqual({ common: 'GGG' }); +}); diff --git a/typings/index.d.ts b/typings/index.d.ts index b2a700502..f47fddee3 100644 --- a/typings/index.d.ts +++ b/typings/index.d.ts @@ -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; + /** + * Return an object containing merged local and global option values as key-value pairs. + */ + optsWithGlobals(): T; + /** * Set the description. * diff --git a/typings/index.test-d.ts b/typings/index.test-d.ts index 09d1c085c..858c8f0bd 100644 --- a/typings/index.test-d.ts +++ b/typings/index.test-d.ts @@ -216,6 +216,18 @@ expectType(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(optsWithGlobals); +expectType(optsWithGlobals.foo); +expectType(optsWithGlobals.bar); + +// optsWithGlobals with generics +const myCheeseOptionWithGlobals = program.optsWithGlobals(); +expectType(myCheeseOptionWithGlobals.cheese); +// @ts-expect-error Check that options strongly typed and does not allow arbitrary properties +expectType(myCheeseOptionWithGlobals.foo); + // description expectType(program.description('my description')); expectType(program.description());