Skip to content

Commit

Permalink
Expand the ts-command-line choice paramter APIs. (#4975)
Browse files Browse the repository at this point in the history
  • Loading branch information
iclanton authored Oct 17, 2024
1 parent 312b8bc commit fdd2659
Show file tree
Hide file tree
Showing 10 changed files with 90 additions and 65 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/ts-command-line",
"comment": "Expand the `alternatives` and `completions` options of `CommandLineChoiceParameter` and `CommandLineChoiceListParameter` to allow readonly arrays and sets.",
"type": "minor"
}
],
"packageName": "@rushstack/ts-command-line"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/ts-command-line",
"comment": "(BREAKING API CHANGE) Change the type of the `alternatives` property of `CommandLineChoiceParameter` and `CommandLineChoiceParameter` from an array to a `ReadonlySet`.",
"type": "minor"
}
],
"packageName": "@rushstack/ts-command-line"
}
20 changes: 10 additions & 10 deletions common/reviews/api/ts-command-line.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,10 @@ export abstract class CommandLineAction extends CommandLineParameterProvider {
export class CommandLineChoiceListParameter<TChoice extends string = string> extends CommandLineParameter {
// @internal
constructor(definition: ICommandLineChoiceListDefinition<TChoice>);
readonly alternatives: ReadonlyArray<TChoice>;
readonly alternatives: ReadonlySet<TChoice>;
// @override
appendToArgList(argList: string[]): void;
readonly completions: (() => Promise<TChoice[]>) | undefined;
readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
readonly kind: CommandLineParameterKind.ChoiceList;
// @internal
_setValue(data: unknown): void;
Expand All @@ -51,10 +51,10 @@ export class CommandLineChoiceListParameter<TChoice extends string = string> ext
export class CommandLineChoiceParameter<TChoice extends string = string> extends CommandLineParameter {
// @internal
constructor(definition: ICommandLineChoiceDefinition<TChoice>);
readonly alternatives: ReadonlyArray<TChoice>;
readonly alternatives: ReadonlySet<TChoice>;
// @override
appendToArgList(argList: string[]): void;
readonly completions: (() => Promise<TChoice[]>) | undefined;
readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;
readonly defaultValue: TChoice | undefined;
// @internal
_getSupplementaryNotes(supplementaryNotes: string[]): void;
Expand Down Expand Up @@ -246,7 +246,7 @@ export abstract class CommandLineParameterWithArgument extends CommandLineParame
// @internal
constructor(definition: IBaseCommandLineDefinitionWithArgument);
readonly argumentName: string;
readonly completions: (() => Promise<string[]>) | undefined;
readonly completions: (() => Promise<ReadonlyArray<string> | ReadonlySet<string>>) | undefined;
}

// @public
Expand Down Expand Up @@ -343,7 +343,7 @@ export interface IBaseCommandLineDefinition {
// @public
export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLineDefinition {
argumentName: string;
completions?: () => Promise<string[]>;
completions?: () => Promise<ReadonlyArray<string> | ReadonlySet<string>>;
}

// @public
Expand All @@ -355,15 +355,15 @@ export interface ICommandLineActionOptions {

// @public
export interface ICommandLineChoiceDefinition<TChoice extends string = string> extends IBaseCommandLineDefinition {
alternatives: TChoice[];
completions?: () => Promise<TChoice[]>;
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;
completions?: () => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>;
defaultValue?: TChoice;
}

// @public
export interface ICommandLineChoiceListDefinition<TChoice extends string = string> extends IBaseCommandLineDefinition {
alternatives: TChoice[];
completions?: () => Promise<TChoice[]>;
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;
completions?: () => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>;
}

// @public
Expand Down
2 changes: 1 addition & 1 deletion libraries/ts-command-line/src/parameters/BaseClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,7 +280,7 @@ export abstract class CommandLineParameterWithArgument extends CommandLineParame
public readonly argumentName: string;

/** {@inheritDoc IBaseCommandLineDefinitionWithArgument.completions} */
public readonly completions: (() => Promise<string[]>) | undefined;
public readonly completions: (() => Promise<ReadonlyArray<string> | ReadonlySet<string>>) | undefined;

/** @internal */
public constructor(definition: IBaseCommandLineDefinitionWithArgument) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,28 +13,30 @@ export class CommandLineChoiceListParameter<
TChoice extends string = string
> extends CommandLineParameterBase {
/** {@inheritDoc ICommandLineChoiceListDefinition.alternatives} */
public readonly alternatives: ReadonlyArray<TChoice>;
public readonly alternatives: ReadonlySet<TChoice>;

private _values: TChoice[] = [];

/** {@inheritDoc ICommandLineChoiceListDefinition.completions} */
public readonly completions: (() => Promise<TChoice[]>) | undefined;
public readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;

/** {@inheritDoc CommandLineParameter.kind} */
public readonly kind: CommandLineParameterKind.ChoiceList = CommandLineParameterKind.ChoiceList;

/** @internal */
public constructor(definition: ICommandLineChoiceListDefinition<TChoice>) {
super(definition);
const { alternatives, completions } = definition;

if (definition.alternatives.length < 1) {
const alternativesSet: Set<TChoice> = alternatives instanceof Set ? alternatives : new Set(alternatives);
if (alternativesSet.size < 1) {
throw new Error(
`When defining a choice list parameter, the alternatives list must contain at least one value.`
);
}

this.alternatives = definition.alternatives;
this.completions = definition.completions;
this.alternatives = alternativesSet;
this.completions = completions;
}

/**
Expand All @@ -60,8 +62,8 @@ export class CommandLineChoiceListParameter<
const values: string[] | undefined = EnvironmentVariableParser.parseAsList(this.environmentVariable);
if (values) {
for (const value of values) {
if (!this.alternatives.includes(value as TChoice)) {
const choices: string = '"' + this.alternatives.join('", "') + '"';
if (!this.alternatives.has(value as TChoice)) {
const choices: string = '"' + Array.from(this.alternatives).join('", "') + '"';
throw new Error(
`Invalid value "${value}" for the environment variable` +
` ${this.environmentVariable}. Valid choices are: ${choices}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,39 +19,41 @@ export interface IRequiredCommandLineChoiceParameter<TChoice extends string = st
*/
export class CommandLineChoiceParameter<TChoice extends string = string> extends CommandLineParameterBase {
/** {@inheritDoc ICommandLineChoiceDefinition.alternatives} */
public readonly alternatives: ReadonlyArray<TChoice>;
public readonly alternatives: ReadonlySet<TChoice>;

/** {@inheritDoc ICommandLineStringDefinition.defaultValue} */
public readonly defaultValue: TChoice | undefined;

private _value: TChoice | undefined = undefined;

/** {@inheritDoc ICommandLineChoiceDefinition.completions} */
public readonly completions: (() => Promise<TChoice[]>) | undefined;
public readonly completions: (() => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>) | undefined;

/** {@inheritDoc CommandLineParameter.kind} */
public readonly kind: CommandLineParameterKind.Choice = -CommandLineParameterKind.Choice;

/** @internal */
public constructor(definition: ICommandLineChoiceDefinition<TChoice>) {
super(definition);
const { alternatives, defaultValue, completions } = definition;

if (definition.alternatives.length < 1) {
const alternativesSet: Set<TChoice> = alternatives instanceof Set ? alternatives : new Set(alternatives);
if (alternativesSet.size < 1) {
throw new Error(
`When defining a choice parameter, the alternatives list must contain at least one value.`
);
}
if (definition.defaultValue && definition.alternatives.indexOf(definition.defaultValue) === -1) {
if (defaultValue && !alternativesSet.has(defaultValue)) {
throw new Error(
`The specified default value "${definition.defaultValue}"` +
` is not one of the available options: ${definition.alternatives.toString()}`
`The specified default value "${defaultValue}"` +
` is not one of the available options: ${alternatives.toString()}`
);
}

this.alternatives = definition.alternatives;
this.defaultValue = definition.defaultValue;
this.alternatives = alternativesSet;
this.defaultValue = defaultValue;
this.validateDefaultValue(!!this.defaultValue);
this.completions = definition.completions;
this.completions = completions;
}

/**
Expand All @@ -72,8 +74,8 @@ export class CommandLineChoiceParameter<TChoice extends string = string> extends
// Try reading the environment variable
const environmentValue: string | undefined = process.env[this.environmentVariable];
if (environmentValue !== undefined && environmentValue !== '') {
if (!this.alternatives.includes(environmentValue as TChoice)) {
const choices: string = '"' + this.alternatives.join('", "') + '"';
if (!this.alternatives.has(environmentValue as TChoice)) {
const choices: string = '"' + Array.from(this.alternatives).join('", "') + '"';
throw new Error(
`Invalid value "${environmentValue}" for the environment variable` +
` ${this.environmentVariable}. Valid choices are: ${choices}`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLine
*
* In a future release, this will be renamed to `getCompletionsAsync`
*/
completions?: () => Promise<string[]>;
completions?: () => Promise<ReadonlyArray<string> | ReadonlySet<string>>;
}

/**
Expand All @@ -146,7 +146,7 @@ export interface ICommandLineChoiceDefinition<TChoice extends string = string>
/**
* A list of strings (which contain no spaces), of possible options which can be selected
*/
alternatives: TChoice[];
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;

/**
* {@inheritDoc ICommandLineStringDefinition.defaultValue}
Expand All @@ -159,7 +159,7 @@ export interface ICommandLineChoiceDefinition<TChoice extends string = string>
* This option is only used when `ICommandLineParserOptions.enableTabCompletionAction`
* is enabled.
*/
completions?: () => Promise<TChoice[]>;
completions?: () => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>;
}

/**
Expand All @@ -174,15 +174,15 @@ export interface ICommandLineChoiceListDefinition<TChoice extends string = strin
/**
* A list of strings (which contain no spaces), of possible options which can be selected
*/
alternatives: TChoice[];
alternatives: ReadonlyArray<TChoice> | ReadonlySet<TChoice>;

/**
* An optional callback that provides a list of custom choices for tab completion.
* @remarks
* This option is only used when `ICommandLineParserOptions.enableTabCompletionAction`
* is enabled.
*/
completions?: () => Promise<TChoice[]>;
completions?: () => Promise<ReadonlyArray<TChoice> | ReadonlySet<TChoice>>;
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,11 +835,11 @@ export abstract class CommandLineParameterProvider {
let type: string | undefined;
switch (kind) {
case CommandLineParameterKind.Choice: {
choices = parameter.alternatives as string[];
choices = Array.from(parameter.alternatives);
break;
}
case CommandLineParameterKind.ChoiceList: {
choices = parameter.alternatives as string[];
choices = Array.from(parameter.alternatives);
action = 'append';
break;
}
Expand Down
22 changes: 11 additions & 11 deletions libraries/ts-command-line/src/providers/TabCompletionAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,10 +122,10 @@ export class TabCompleteAction extends CommandLineAction {
if (completePartialWord) {
for (const parameterName of parameterNames) {
if (parameterName === secondLastToken) {
const values: ReadonlyArray<string> = await this._getParameterValueCompletionsAsync(
const values: ReadonlySet<string> = await this._getParameterValueCompletionsAsync(
parameterNameMap.get(parameterName)!
);
if (values.length > 0) {
if (values.size > 0) {
yield* this._completeParameterValues(values, lastToken);
return;
}
Expand All @@ -135,10 +135,10 @@ export class TabCompleteAction extends CommandLineAction {
} else {
for (const parameterName of parameterNames) {
if (parameterName === lastToken) {
const values: ReadonlyArray<string> = await this._getParameterValueCompletionsAsync(
const values: ReadonlySet<string> = await this._getParameterValueCompletionsAsync(
parameterNameMap.get(parameterName)!
);
if (values.length > 0) {
if (values.size > 0) {
yield* values;
return;
}
Expand Down Expand Up @@ -174,8 +174,8 @@ export class TabCompleteAction extends CommandLineAction {

private async _getParameterValueCompletionsAsync(
parameter: CommandLineParameter
): Promise<ReadonlyArray<string>> {
let choiceParameterValues: ReadonlyArray<string> = [];
): Promise<ReadonlySet<string>> {
let choiceParameterValues: ReadonlySet<string> | undefined;
if (parameter.kind === CommandLineParameterKind.Choice) {
choiceParameterValues = parameter.alternatives;
} else if (parameter.kind !== CommandLineParameterKind.Flag) {
Expand All @@ -190,12 +190,12 @@ export class TabCompleteAction extends CommandLineAction {
parameterWithArgumentOrChoices = parameter;
}

if (parameterWithArgumentOrChoices?.completions) {
choiceParameterValues = await parameterWithArgumentOrChoices.completions();
}
const completionValues: ReadonlyArray<string> | ReadonlySet<string> | undefined =
await parameterWithArgumentOrChoices?.completions?.();
choiceParameterValues = completionValues instanceof Set ? completionValues : new Set(completionValues);
}

return choiceParameterValues;
return choiceParameterValues ?? new Set();
}

private _getGlobalParameterOffset(tokens: string[]): number {
Expand All @@ -215,7 +215,7 @@ export class TabCompleteAction extends CommandLineAction {
}

private *_completeParameterValues(
choiceParameterValues: ReadonlyArray<string>,
choiceParameterValues: ReadonlyArray<string> | ReadonlySet<string>,
lastToken: string
): IterableIterator<string> {
for (const choiceParameterValue of choiceParameterValues) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,32 +173,33 @@ export const ParameterForm = (): JSX.Element => {

switch (parameter.kind) {
case CommandLineParameterKind.Choice: {
const commandLineChoiceParameter: CommandLineChoiceParameter =
const { alternatives, defaultValue }: CommandLineChoiceParameter =
parameter as CommandLineChoiceParameter;
const options: { key: string; text: string }[] = [];
for (const alternative of alternatives) {
options.push({
key: alternative,
text: alternative
});
}

fieldNode = (
<ControlledComboBox
{...baseControllerProps}
defaultValue={commandLineChoiceParameter.defaultValue}
options={commandLineChoiceParameter.alternatives.map((alternative: string) => ({
key: alternative,
text: alternative
}))}
/>
<ControlledComboBox {...baseControllerProps} defaultValue={defaultValue} options={options} />
);
break;
}
case CommandLineParameterKind.ChoiceList: {
const commandLineChoiceListParameter: CommandLineChoiceListParameter =
const { alternatives }: CommandLineChoiceListParameter =
parameter as CommandLineChoiceListParameter;
const options: { key: string; text: string }[] = [];
for (const alternative of alternatives) {
options.push({
key: alternative,
text: alternative
});
}
fieldNode = (
<ControlledComboBox
{...baseControllerProps}
multiSelect={true}
options={commandLineChoiceListParameter.alternatives.map((alternative: string) => ({
key: alternative,
text: alternative
}))}
/>
<ControlledComboBox {...baseControllerProps} multiSelect={true} options={options} />
);
break;
}
Expand Down

0 comments on commit fdd2659

Please sign in to comment.