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

Clean up state from previous parse call when calling parse() / parseAsync() #1919

Closed
wants to merge 18 commits into from
Closed
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
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
113 changes: 89 additions & 24 deletions lib/command.js
Expand Up @@ -10,6 +10,12 @@ const { Help } = require('./help.js');
const { Option, splitOptionFlags, DualOptions } = require('./option.js');
const { suggestSimilar } = require('./suggestSimilar');

/**
aweebit marked this conversation as resolved.
Show resolved Hide resolved
* TypeScript import types for JSDoc, used by Visual Studio Code IntelliSense and `npm run typescript-checkJS`
* https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#import-types
* @typedef { import('../typings').OptionValueSource } OptionValueSource
*/

// @ts-check

class Command extends EventEmitter {
Expand All @@ -30,14 +36,6 @@ class Command extends EventEmitter {
this._allowExcessArguments = true;
/** @type {Argument[]} */
this._args = [];
/** @type {string[]} */
this.args = []; // cli args with options removed
this.rawArgs = [];
this.processedArgs = []; // like .args but after custom processing and collecting variadic
this._scriptPath = null;
this._name = name || '';
this._optionValues = {};
this._optionValueSources = {}; // default, env, cli etc
this._storeOptionsAsProperties = false;
this._actionHandler = null;
this._executableHandler = false;
Expand Down Expand Up @@ -77,6 +75,25 @@ class Command extends EventEmitter {
this._helpCommandnameAndArgs = 'help [command]';
this._helpCommandDescription = 'display help for command';
this._helpConfiguration = {};

this._unprocessedName = name || '';
this._persistentOptionValues = {};
this._persistentOptionValueSources = {};
this.resetParseState();
}

resetParseState() {
/** @type {string[]} */
this.args = []; // cli args with options removed
this.rawArgs = [];
this.processedArgs = []; // like .args but after custom processing and collecting variadic
this._scriptPath = null;

this._name = this._unprocessedName;
this._optionValues = Object.assign({}, this._persistentOptionValues);
this._optionValueSources = Object.assign(
{}, this._persistentOptionValueSources
); // default, env, cli etc
}

/**
Expand Down Expand Up @@ -558,7 +575,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
val = ''; // not normal, parseArg might have failed or be a mock function for testing
}
}
this.setOptionValueWithSource(name, val, valueSource);
this._setNonPersistentOptionValueWithSource(name, val, valueSource);
};

this.on('option:' + oname, (val) => {
Expand Down Expand Up @@ -780,34 +797,64 @@ Expecting one of '${allowedValues.join("', '")}'`);
*/

setOptionValue(key, value) {
return this.setOptionValueWithSource(key, value, undefined);
this._setPersistentOptionValueWithSource(key, value);
return this;
}

/**
* Store option value and where the value came from.
*
* @param {string} key
* @param {Object} value
* @param {string} source - expected values are default/config/env/cli/implied
* @param {'config'} source
* @return {Command} `this` command for chaining
*/

setOptionValueWithSource(key, value, source) {
this._setPersistentOptionValueWithSource(key, value, source);
return this;
}

/**
* Store option value and where the value came from.
*
* @param {string} key
* @param {Object} value
* @param {'default' | 'config'} [source]
* @api private
*/

_setPersistentOptionValueWithSource(key, value, source) {
this._setNonPersistentOptionValueWithSource(key, value, source);
this._persistentOptionValues[key] = value;
this._persistentOptionValueSources[key] = source;
}

/**
* @param {string} key
* @param {Object} value
* @param {OptionValueSource} [source]
* @api private
*/

_setNonPersistentOptionValueWithSource(key, value, source) {
if (this._storeOptionsAsProperties) {
this[key] = value;
} else {
this._optionValues[key] = value;
}
this._optionValueSources[key] = source;
return this;

delete this._persistentOptionValues[key];
aweebit marked this conversation as resolved.
Show resolved Hide resolved
delete this._persistentOptionValueSources[key];
}

/**
* Get source of option value.
* Expected values are default | config | env | cli | implied
*
* @param {string} key
* @return {string}
* @return {OptionValueSource | undefined}
*/

getOptionValueSource(key) {
Expand All @@ -819,7 +866,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
* Expected values are default | config | env | cli | implied
*
* @param {string} key
* @return {string}
* @return {OptionValueSource | undefined}
*/

getOptionValueSourceWithGlobals(key) {
Expand Down Expand Up @@ -887,6 +934,22 @@ Expecting one of '${allowedValues.join("', '")}'`);
return userArgs;
}

/**
* @param {boolean} async
* @param {Function} userArgsCallback
* @param {string[]} [argv]
* @param {Object} [parseOptions]
* @param {string} [parseOptions.from]
* @return {Command|Promise}
* @api private
*/

_parseSubroutine(async, userArgsCallback, argv, parseOptions) {
this.resetParseState();
const userArgs = this._prepareUserArgs(argv, parseOptions);
return userArgsCallback(userArgs);
}

/**
* Parse `argv`, setting options and invoking commands when defined.
*
Expand All @@ -905,10 +968,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
*/

parse(argv, parseOptions) {
const userArgs = this._prepareUserArgs(argv, parseOptions);
this._parseCommand([], userArgs);

return this;
return this._parseSubroutine(false, (userArgs) => {
this._parseCommand([], userArgs);
return this;
}, argv, parseOptions);
}

/**
Expand All @@ -931,10 +994,10 @@ Expecting one of '${allowedValues.join("', '")}'`);
*/

async parseAsync(argv, parseOptions) {
const userArgs = this._prepareUserArgs(argv, parseOptions);
await this._parseCommand([], userArgs);

return this;
return this._parseSubroutine(true, async(userArgs) => {
await this._parseCommand([], userArgs);
return this;
}, argv, parseOptions);
}

/**
Expand Down Expand Up @@ -1650,7 +1713,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
Object.keys(option.implied)
.filter(impliedKey => !hasCustomOptionValue(impliedKey))
.forEach(impliedKey => {
this.setOptionValueWithSource(impliedKey, option.implied[impliedKey], 'implied');
this._setNonPersistentOptionValueWithSource(
impliedKey, option.implied[impliedKey], 'implied'
);
});
});
}
Expand Down Expand Up @@ -1932,7 +1997,7 @@ Expecting one of '${allowedValues.join("', '")}'`);

name(str) {
if (str === undefined) return this._name;
this._name = str;
this._name = this._unprocessedName = str;
return this;
}

Expand Down
2 changes: 1 addition & 1 deletion typings/index.d.ts
Expand Up @@ -595,7 +595,7 @@ export class Command {
/**
* Store option value and where the value came from.
*/
setOptionValueWithSource(key: string, value: unknown, source: OptionValueSource): this;
setOptionValueWithSource(key: string, value: unknown, source: 'config'): this;

/**
* Get source of option value.
Expand Down
2 changes: 1 addition & 1 deletion typings/index.test-d.ts
Expand Up @@ -170,7 +170,7 @@ expectType<commander.Command>(program.setOptionValue('example', 'value'));
expectType<commander.Command>(program.setOptionValue('example', true));

// setOptionValueWithSource
expectType<commander.Command>(program.setOptionValueWithSource('example', [], 'cli'));
expectType<commander.Command>(program.setOptionValueWithSource('example', [], 'config'));

// getOptionValueSource
expectType<commander.OptionValueSource | undefined>(program.getOptionValueSource('example'));
Expand Down