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 Option.implies() #1724

Merged
merged 13 commits into from May 11, 2022
22 changes: 22 additions & 0 deletions lib/command.js
Expand Up @@ -1194,6 +1194,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
_parseCommand(operands, unknown) {
const parsed = this.parseOptions(unknown);
this._parseOptionsEnv(); // after cli, so parseArg not called on both cli and env
this._parseOptionsImplied();
operands = operands.concat(parsed.operands);
unknown = parsed.unknown;
this.args = operands.concat(unknown);
Expand Down Expand Up @@ -1569,6 +1570,27 @@ Expecting one of '${allowedValues.join("', '")}'`);
});
}

/**
* Apply any implied option values, if option is undefined or default value.
*
* @api private
*/
_parseOptionsImplied() {
// ToDo: how much effort for positive/negative options????
const customOptionValue = (optionKey) => {
return this.getOptionValue(optionKey) !== undefined && !['default'].includes(this.getOptionValueSource(optionKey));
};
this.options
.filter(option => (option.implied !== undefined) && customOptionValue(option.attributeName()))
.forEach((option) => {
Object.keys(option.implied)
.filter(impliedKey => !customOptionValue(impliedKey))
.forEach(impliedKey => {
this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], 'implied');
});
});
}

/**
* Argument `name` is missing.
*
Expand Down
17 changes: 17 additions & 0 deletions lib/option.js
Expand Up @@ -34,6 +34,7 @@ class Option {
this.hidden = false;
this.argChoices = undefined;
this.conflictsWith = [];
this.implied = undefined;
}

/**
Expand Down Expand Up @@ -84,6 +85,22 @@ class Option {
return this;
}

/**
* Specify implied option values for when this option is set and the implied option is not.
*
* @example
* program
* .addOption(new Option('--quiet').implies({ logLevel: 'off' }))
* .addOption(new Option('--log-level <level>').choices(['info', 'warning', 'error', 'off']).default('info'));
*
* @param {Object} impliedOptionValues
* @return {Option}
*/
implies(impliedOptionValues) {
this.implied = Object.assign(this.implied || {}, impliedOptionValues);
return this;
}

/**
* Set environment variable to check for option value.
* Priority order of option values is default < env < cli
Expand Down
67 changes: 67 additions & 0 deletions tests/options.implies.test.js
@@ -0,0 +1,67 @@
const { Command, Option } = require('../');

describe('check priotities', () => {
test('when source undefined and implied undefined then implied is undefined', () => {
const program = new Command();
program
.addOption(new Option('--foo').implies({ bar: 'implied' }))
.option('--bar');
program.parse([], { from: 'user' });
expect(program.opts()).toEqual({});
});

test('when source default and implied undefined then implied is undefined', () => {
const program = new Command();
program
.addOption(new Option('--foo').implies({ bar: 'implied' }).default('default'))
.option('--bar');
program.parse([], { from: 'user' });
expect(program.opts()).toEqual({ foo: 'default' });
});

test('when source from env and implied undefined then implied is implied', () => {
const program = new Command();
const envName = 'COMMANDER_TEST_DELETE_ME';
process.env[envName] = 'env';
program
.addOption(new Option('--foo').implies({ bar: 'implied' }).env(envName))
.option('--bar');
program.parse([], { from: 'user' });
expect(program.opts()).toEqual({ foo: true, bar: 'implied' });
delete process.env[envName];
});

test('when source from cli and implied undefined then implied is implied', () => {
const program = new Command();
program
.addOption(new Option('--foo').implies({ bar: 'implied' }))
.option('--bar');
program.parse(['--foo'], { from: 'user' });
expect(program.opts()).toEqual({ foo: true, bar: 'implied' });
});

test('when source cli and implied default then implied is implied', () => {
const program = new Command();
program
.addOption(new Option('--foo').implies({ bar: 'implied' }))
.option('--bar', '', 'default');
program.parse(['--foo'], { from: 'user' });
expect(program.opts()).toEqual({ foo: true, bar: 'implied' });
});

test('when source cli and env default then implied is env', () => {
const program = new Command();
const envName = 'COMMANDER_TEST_DELETE_ME';
process.env[envName] = 'env';
program
.addOption(new Option('--foo').implies({ bar: 'implied' }))
.addOption(new Option('--bar <value>').env(envName));
program.parse(['--foo'], { from: 'user' });
expect(program.opts()).toEqual({ foo: true, bar: 'env' });
delete process.env[envName];
});
});

// Can store a value that is not actually an option (!)

// Can store more than one implied key