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 getOptionValueSourceWithGlobals #1832

Merged
merged 1 commit into from Dec 19, 2022
Merged
Show file tree
Hide file tree
Changes from all 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
19 changes: 19 additions & 0 deletions lib/command.js
Expand Up @@ -814,6 +814,25 @@ Expecting one of '${allowedValues.join("', '")}'`);
return this._optionValueSources[key];
}

/**
* Get source of option value. See also .optsWithGlobals().
* Expected values are default | config | env | cli | implied
*
* @param {string} key
* @return {string}
*/

getOptionValueSourceWithGlobals(key) {
// global overwrites local, like optsWithGlobals
let source;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For reference :
You can also write the following using findLast.

    return getCommandAndParents(this)
      .findLast((cmd) => cmd.getOptionValueSource(key) !== undefined)
      .getOptionValueSource(key);

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can use Node.js 18 or later.
My apologies.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I started out planning to target Commander v10.x just so I could use ??, but switched to old-school Node 12 compatible code for now. Eventually!

getCommandAndParents(this).forEach((cmd) => {
if (cmd.getOptionValueSource(key) !== undefined) {
source = cmd.getOptionValueSource(key);
}
});
return source;
}

/**
* Get user arguments from implied or explicit arguments.
* Side-effects: set _scriptPath if args included script. Used for default program name, and subcommand searches.
Expand Down
164 changes: 108 additions & 56 deletions tests/options.optsWithGlobals.test.js
@@ -1,63 +1,115 @@
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());
});
// Testing optsWithGlobals and getOptionValueSourceWithGlobals with focus on globals.

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' });
});
describe('optsWithGlobals', () => {
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();
});

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' });
program.parse(['-c', 'GGG', 'sub', '-c', 'LLL'], { from: 'user' });
expect(mergedOptions).toEqual({ common: 'GGG' });
});
});

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' });
describe('getOptionValueSourceWithGlobals', () => {
test('when option used with simple command then source is defined', () => {
const program = new commander.Command();
program
.option('-g, --global');

program.parse(['-g'], { from: 'user' });
expect(program.getOptionValueSourceWithGlobals('global')).toEqual('cli');
});

test('when option used with program then source is defined', () => {
const program = new commander.Command();
program
.option('-g, --global');
const sub = program.command('sub')
.option('-l, --local')
.action(() => {});

program.parse(['sub', '-g'], { from: 'user' });
expect(sub.getOptionValueSourceWithGlobals('global')).toEqual('cli');
});

test('when option used with subcommand then source is defined', () => {
const program = new commander.Command();
program
.option('-g, --global');
const sub = program.command('sub')
.option('-l, --local')
.action(() => {});

program.parse(['sub', '-l'], { from: 'user' });
expect(sub.getOptionValueSourceWithGlobals('local')).toEqual('cli');
});

test('when same named option in sub and program then source is defined by global', () => {
const program = new commander.Command();
program
.enablePositionalOptions()
.option('-c, --common <value>', 'description', 'default value');
const sub = program.command('sub')
.option('-c, --common <value>')
.action(() => {});

program.parse(['sub', '--common', 'value'], { from: 'user' });
expect(sub.getOptionValueSourceWithGlobals('common')).toEqual('default');
});
});
7 changes: 6 additions & 1 deletion typings/index.d.ts
Expand Up @@ -600,10 +600,15 @@ export class Command {
setOptionValueWithSource(key: string, value: unknown, source: OptionValueSource): this;

/**
* Retrieve option value source.
* Get source of option value.
*/
getOptionValueSource(key: string): OptionValueSource | undefined;

/**
* Get source of option value. See also .optsWithGlobals().
*/
getOptionValueSourceWithGlobals(key: string): OptionValueSource | undefined;

/**
* Alter parsing of short flags with optional values.
*
Expand Down
3 changes: 3 additions & 0 deletions typings/index.test-d.ts
Expand Up @@ -174,6 +174,9 @@ expectType<commander.Command>(program.setOptionValueWithSource('example', [], 'c
// getOptionValueSource
expectType<commander.OptionValueSource | undefined>(program.getOptionValueSource('example'));

// getOptionValueSourceWithGlobals
expectType<commander.OptionValueSource | undefined>(program.getOptionValueSourceWithGlobals('example'));

// combineFlagAndOptionalValue
expectType<commander.Command>(program.combineFlagAndOptionalValue());
expectType<commander.Command>(program.combineFlagAndOptionalValue(false));
Expand Down