Skip to content
Merged
22 changes: 22 additions & 0 deletions lib/command.js
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
@@ -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