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

[ts-command-line] Add ScopedCommandLineAction class #3364

Merged
merged 19 commits into from
May 10, 2022
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/ts-command-line",
"comment": "Add ScopedCommandLineAction class, which allows for the creation of actions that have variable arguments based on the provided scope.",
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
"type": "minor"
}
],
"packageName": "@rushstack/ts-command-line"
}
29 changes: 27 additions & 2 deletions common/reviews/api/ts-command-line.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ export abstract class CommandLineAction extends CommandLineParameterProvider {
protected abstract onDefineParameters(): void;
protected abstract onExecute(): Promise<void>;
// @internal
_processParsedData(data: _ICommandLineParserData): void;
_processParsedData(parserOptions: ICommandLineParserOptions, data: _ICommandLineParserData): void;
readonly summary: string;
}

Expand Down Expand Up @@ -113,6 +113,7 @@ export abstract class CommandLineParameter {
readonly environmentVariable: string | undefined;
// @internal
_getSupplementaryNotes(supplementaryNotes: string[]): void;
readonly groupName: string | undefined;
abstract get kind(): CommandLineParameterKind;
readonly longName: string;
// @internal
Expand Down Expand Up @@ -148,6 +149,8 @@ export abstract class CommandLineParameterProvider {
defineFlagParameter(definition: ICommandLineFlagDefinition): CommandLineFlagParameter;
defineIntegerListParameter(definition: ICommandLineIntegerListDefinition): CommandLineIntegerListParameter;
defineIntegerParameter(definition: ICommandLineIntegerDefinition): CommandLineIntegerParameter;
// @internal (undocumented)
protected _defineParameter(parameter: CommandLineParameter): void;
defineStringListParameter(definition: ICommandLineStringListDefinition): CommandLineStringListParameter;
defineStringParameter(definition: ICommandLineStringDefinition): CommandLineStringParameter;
// @internal
Expand All @@ -164,9 +167,10 @@ export abstract class CommandLineParameterProvider {
get parameters(): ReadonlyArray<CommandLineParameter>;
get parametersProcessed(): boolean;
// @internal (undocumented)
protected _processParsedData(data: _ICommandLineParserData): void;
protected _processParsedData(parserOptions: ICommandLineParserOptions, data: _ICommandLineParserData): void;
get remainder(): CommandLineRemainder | undefined;
renderHelpText(): string;
renderUsageText(): string;
}

// @public
Expand Down Expand Up @@ -249,6 +253,7 @@ export class DynamicCommandLineParser extends CommandLineParser {
export interface IBaseCommandLineDefinition {
description: string;
environmentVariable?: string;
parameterGroupName?: string;
parameterLongName: string;
parameterShortName?: string;
required?: boolean;
Expand Down Expand Up @@ -306,6 +311,7 @@ export interface _ICommandLineParserData {
export interface ICommandLineParserOptions {
enableTabCompletionAction?: boolean;
toolDescription: string;
toolEpilog?: string;
toolFilename: string;
}

Expand All @@ -323,4 +329,23 @@ export interface ICommandLineStringDefinition extends IBaseCommandLineDefinition
export interface ICommandLineStringListDefinition extends IBaseCommandLineDefinitionWithArgument {
}

// @public
export abstract class ScopedCommandLineAction extends CommandLineAction {
constructor(options: ICommandLineActionOptions);
// @internal (undocumented)
protected _defineParameter(parameter: CommandLineParameter): void;
// @internal
_execute(): Promise<void>;
// @internal
protected _getScopedCommandLineParser(): CommandLineParser;
protected onDefineParameters(): void;
protected abstract onDefineScopedParameters(scopedParameterProvider: CommandLineParameterProvider): void;
protected abstract onDefineUnscopedParameters(): void;
protected abstract onExecute(): Promise<void>;
get parameters(): ReadonlyArray<CommandLineParameter>;
// @internal
_processParsedData(parserOptions: ICommandLineParserOptions, data: _ICommandLineParserData): void;
static ScopingParameterGroupName: 'scoping';
D4N14L marked this conversation as resolved.
Show resolved Hide resolved
}

```
5 changes: 2 additions & 3 deletions libraries/ts-command-line/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
*/

export { CommandLineAction, ICommandLineActionOptions } from './providers/CommandLineAction';
export { DynamicCommandLineAction } from './providers/DynamicCommandLineAction';
export { ScopedCommandLineAction } from './providers/ScopedCommandLineAction';

export {
IBaseCommandLineDefinition,
Expand Down Expand Up @@ -43,9 +45,6 @@ export {
} from './providers/CommandLineParameterProvider';

export { ICommandLineParserOptions, CommandLineParser } from './providers/CommandLineParser';

export { DynamicCommandLineAction } from './providers/DynamicCommandLineAction';

export { DynamicCommandLineParser } from './providers/DynamicCommandLineParser';

export { CommandLineConstants } from './Constants';
Expand Down
4 changes: 4 additions & 0 deletions libraries/ts-command-line/src/parameters/BaseClasses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ export abstract class CommandLineParameter {
/** {@inheritDoc IBaseCommandLineDefinition.parameterShortName} */
public readonly shortName: string | undefined;

/** {@inheritDoc IBaseCommandLineDefinition.parameterGroupName} */
public readonly groupName: string | undefined;

/** {@inheritDoc IBaseCommandLineDefinition.description} */
public readonly description: string;

Expand All @@ -69,6 +72,7 @@ export abstract class CommandLineParameter {
public constructor(definition: IBaseCommandLineDefinition) {
this.longName = definition.parameterLongName;
this.shortName = definition.parameterShortName;
this.groupName = definition.parameterGroupName;
this.description = definition.description;
this.required = !!definition.required;
this.environmentVariable = definition.environmentVariable;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ export interface IBaseCommandLineDefinition {
*/
parameterShortName?: string;

/**
* An optional parameter group name, shown when invoking the tool with "--help"
*/
parameterGroupName?: string;

/**
* Documentation for the parameter that will be shown when invoking the tool with "--help"
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
// See LICENSE in the project root for license information.

import * as argparse from 'argparse';

import { CommandLineParameterProvider, ICommandLineParserData } from './CommandLineParameterProvider';
import type { ICommandLineParserOptions } from './CommandLineParser';

/**
* Options for the CommandLineAction constructor.
Expand Down Expand Up @@ -90,8 +92,8 @@ export abstract class CommandLineAction extends CommandLineParameterProvider {
* This is called internally by CommandLineParser.execute()
* @internal
*/
public _processParsedData(data: ICommandLineParserData): void {
super._processParsedData(data);
public _processParsedData(parserOptions: ICommandLineParserOptions, data: ICommandLineParserData): void {
super._processParsedData(parserOptions, data);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
// See LICENSE in the project root for license information.

import * as argparse from 'argparse';
import {

import type {
ICommandLineChoiceDefinition,
ICommandLineChoiceListDefinition,
ICommandLineIntegerDefinition,
Expand All @@ -12,6 +13,7 @@ import {
ICommandLineStringListDefinition,
ICommandLineRemainderDefinition
} from '../parameters/CommandLineDefinition';
import type { ICommandLineParserOptions } from './CommandLineParser';
import {
CommandLineParameter,
CommandLineParameterWithArgument,
Expand Down Expand Up @@ -46,6 +48,7 @@ export abstract class CommandLineParameterProvider {

private _parameters: CommandLineParameter[];
private _parametersByLongName: Map<string, CommandLineParameter>;
private _parameterGroupsByName: Map<string, argparse.ArgumentGroup>;
private _parametersProcessed: boolean;
private _remainder: CommandLineRemainder | undefined;

Expand All @@ -54,6 +57,7 @@ export abstract class CommandLineParameterProvider {
public constructor() {
this._parameters = [];
this._parametersByLongName = new Map<string, CommandLineParameter>();
this._parameterGroupsByName = new Map<string, argparse.ArgumentGroup>();
this._parametersProcessed = false;
}

Expand Down Expand Up @@ -207,6 +211,7 @@ export abstract class CommandLineParameterProvider {
public getIntegerListParameter(parameterLongName: string): CommandLineIntegerListParameter {
return this._getParameter(parameterLongName, CommandLineParameterKind.IntegerList);
}

/**
* Defines a command-line parameter whose argument is a single text string.
*
Expand Down Expand Up @@ -297,6 +302,13 @@ export abstract class CommandLineParameterProvider {
return this._getArgumentParser().formatHelp();
}

/**
* Generates the command-line usage text.
*/
public renderUsageText(): string {
return this._getArgumentParser().formatUsage();
}

/**
* Returns a object which maps the long name of each parameter in this.parameters
* to the stringified form of its value. This is useful for logging telemetry, but
Expand Down Expand Up @@ -349,7 +361,7 @@ export abstract class CommandLineParameterProvider {
protected abstract _getArgumentParser(): argparse.ArgumentParser;

/** @internal */
protected _processParsedData(data: ICommandLineParserData): void {
protected _processParsedData(parserOptions: ICommandLineParserOptions, data: ICommandLineParserData): void {
if (this._parametersProcessed) {
throw new Error('Command Line Parser Data was already processed');
}
Expand All @@ -367,28 +379,8 @@ export abstract class CommandLineParameterProvider {
this._parametersProcessed = true;
}

private _generateKey(): string {
return 'key_' + (CommandLineParameterProvider._keyCounter++).toString();
}

private _getParameter<T extends CommandLineParameter>(
parameterLongName: string,
expectedKind: CommandLineParameterKind
): T {
const parameter: CommandLineParameter | undefined = this._parametersByLongName.get(parameterLongName);
if (!parameter) {
throw new Error(`The parameter "${parameterLongName}" is not defined`);
}
if (parameter.kind !== expectedKind) {
throw new Error(
`The parameter "${parameterLongName}" is of type "${CommandLineParameterKind[parameter.kind]}"` +
` whereas the caller was expecting "${CommandLineParameterKind[expectedKind]}".`
);
}
return parameter as T;
}

private _defineParameter(parameter: CommandLineParameter): void {
/** @internal */
protected _defineParameter(parameter: CommandLineParameter): void {
if (this._remainder) {
throw new Error(
'defineCommandLineRemainder() was already called for this provider;' +
Expand Down Expand Up @@ -455,10 +447,23 @@ export abstract class CommandLineParameterProvider {
break;
}

const argumentParser: argparse.ArgumentParser = this._getArgumentParser();
argumentParser.addArgument(names, { ...argparseOptions });
let argumentGroup: argparse.ArgumentGroup | undefined;
if (parameter.groupName) {
argumentGroup = this._parameterGroupsByName.get(parameter.groupName);
if (!argumentGroup) {
argumentGroup = this._getArgumentParser().addArgumentGroup({
title: `Optional ${parameter.groupName} arguments`
});
this._parameterGroupsByName.set(parameter.groupName, argumentGroup);
}
} else {
argumentGroup = this._getArgumentParser();
}

argumentGroup.addArgument(names, { ...argparseOptions });

if (parameter.undocumentedSynonyms && parameter.undocumentedSynonyms.length > 0) {
argumentParser.addArgument(parameter.undocumentedSynonyms, {
argumentGroup.addArgument(parameter.undocumentedSynonyms, {
...argparseOptions,
help: argparse.Const.SUPPRESS
});
Expand All @@ -467,4 +472,25 @@ export abstract class CommandLineParameterProvider {
this._parameters.push(parameter);
this._parametersByLongName.set(parameter.longName, parameter);
}

private _generateKey(): string {
return 'key_' + (CommandLineParameterProvider._keyCounter++).toString();
}

private _getParameter<T extends CommandLineParameter>(
parameterLongName: string,
expectedKind: CommandLineParameterKind
): T {
const parameter: CommandLineParameter | undefined = this._parametersByLongName.get(parameterLongName);
if (!parameter) {
throw new Error(`The parameter "${parameterLongName}" is not defined`);
}
if (parameter.kind !== expectedKind) {
throw new Error(
`The parameter "${parameterLongName}" is of type "${CommandLineParameterKind[parameter.kind]}"` +
` whereas the caller was expecting "${CommandLineParameterKind[expectedKind]}".`
);
}
return parameter as T;
}
}
17 changes: 13 additions & 4 deletions libraries/ts-command-line/src/providers/CommandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ export interface ICommandLineParserOptions {
*/
toolDescription: string;

/**
* An optional string to append at the end of the "--help" main page. If not provided, an epilog
* will be automatically generated based on the toolFilename.
*/
toolEpilog?: string;

/**
* Set to true to auto-define a tab completion action. False by default.
*/
Expand Down Expand Up @@ -68,7 +74,8 @@ export abstract class CommandLineParser extends CommandLineParameterProvider {
prog: this._options.toolFilename,
description: this._options.toolDescription,
epilog: colors.bold(
`For detailed help about a specific command, use: ${this._options.toolFilename} <command> -h`
this._options.toolEpilog ??
`For detailed help about a specific command, use: ${this._options.toolFilename} <command> -h`
)
});

Expand Down Expand Up @@ -194,19 +201,21 @@ export abstract class CommandLineParser extends CommandLineParameterProvider {
// 0=node.exe, 1=script name
args = process.argv.slice(2);
}
if (args.length === 0) {
if (this.actions.length > 0 && args.length === 0) {
// Parsers that use actions should print help when 0 args are provided. Allow
// actionless parsers to continue on zero args.
this._argumentParser.printHelp();
return;
}

const data: ICommandLineParserData = this._argumentParser.parseArgs(args);

this._processParsedData(data);
this._processParsedData(this._options, data);

for (const action of this._actions) {
if (action.actionName === data.action) {
this.selectedAction = action;
action._processParsedData(data);
action._processParsedData(this._options, data);
break;
}
}
Expand Down
Loading