Skip to content

Commit 0ce33aa

Browse files
committed
Added CommandLineParameter.test.ts and validation for parameter names
1 parent 21fe90c commit 0ce33aa

9 files changed

Lines changed: 195 additions & 52 deletions

common/reviews/api/ts-command-line.api.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,12 +86,13 @@ class CommandLineParameterProvider {
8686
getStringListParameter(parameterLongName: string): CommandLineStringListParameter;
8787
getStringParameter(parameterLongName: string): CommandLineStringParameter;
8888
protected abstract onDefineParameters(): void;
89+
renderHelpText(): string;
8990
}
9091

9192
// @public
9293
class CommandLineParser extends CommandLineParameterProvider {
9394
constructor(options: ICommandLineParserOptions);
94-
addAction(command: CommandLineAction): void;
95+
addAction(action: CommandLineAction): void;
9596
execute(args?: string[]): Promise<boolean>;
9697
executeWithoutErrorHandling(args?: string[]): Promise<void>;
9798
protected onExecute(): Promise<void>;

libraries/ts-command-line/src/CommandLineDefinition.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,8 +28,9 @@ export interface IBaseCommandLineDefinitionWithArgument extends IBaseCommandLine
2828
* The name of the argument, which will be shown in the command-line help.
2929
*
3030
* @remarks
31-
* Suppose the help shows "--output FILE". Then "--output' is the parameter name,
32-
* and "FILE" is the argument name.
31+
* For example, if the parameter name is '--count" and the argument name is "NUMBER",
32+
* then the command-line help would display "--count NUMBER". The argument name must
33+
* be comprised of upper-case letters, numbers, and underscores. It should be kept short.
3334
*/
3435
argumentName: string;
3536
}

libraries/ts-command-line/src/CommandLineParameter.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ export enum CommandLineParameterKind {
3333
* @public
3434
*/
3535
export abstract class CommandLineParameter<T> {
36+
private static _longNameRegExp: RegExp = /^-(-[a-z0-9]+)+$/;
37+
private static _shortNameRegExp: RegExp = /^-[a-zA-Z0-9]$/;
38+
3639
/**
3740
* A unique internal key used to retrieve the value from the parser's dictionary.
3841
* @internal
@@ -52,7 +55,18 @@ export abstract class CommandLineParameter<T> {
5255

5356
/** @internal */
5457
constructor(definition: IBaseCommandLineDefinition) {
58+
if (!CommandLineParameter._longNameRegExp.test(definition.parameterLongName)) {
59+
throw new Error(`Invalid name: "${definition.parameterLongName}". The parameter long name must be`
60+
+ ` lower-case and use dash delimiters (e.g. "--do-a-thing")`);
61+
}
5562
this.longName = definition.parameterLongName;
63+
64+
if (definition.parameterShortName) {
65+
if (!CommandLineParameter._shortNameRegExp.test(definition.parameterShortName)) {
66+
throw new Error(`Invalid name: "${definition.parameterShortName}". The parameter short name must be`
67+
+ ` a dash followed by a single letter (e.g. "-a")`);
68+
}
69+
}
5670
this.shortName = definition.parameterShortName;
5771
this.description = definition.description;
5872
}
@@ -83,12 +97,27 @@ export abstract class CommandLineParameter<T> {
8397
}
8498

8599
export abstract class CommandLineParameterWithArgument<T> extends CommandLineParameter<T> {
100+
private static _invalidArgumentNameRegExp: RegExp = /[^A-Z_0-9]/;
101+
86102
/** {@inheritdoc IBaseCommandLineDefinitionWithArgument.argumentName} */
87-
public readonly argumentName: string;
103+
public readonly argumentName: string | undefined;
88104

89105
/** @internal */
90106
constructor(definition: IBaseCommandLineDefinitionWithArgument) {
91107
super(definition);
108+
109+
if (definition.argumentName === '') {
110+
throw new Error('The argument name cannot be an empty string. (For the default name, specify undefined.)');
111+
}
112+
if (definition.argumentName.toUpperCase() !== definition.argumentName) {
113+
throw new Error(`Invalid name: "${definition.argumentName}". The argument name must be all upper case.`);
114+
}
115+
const match: RegExpMatchArray | null = definition.argumentName.match(
116+
CommandLineParameterWithArgument._invalidArgumentNameRegExp);
117+
if (match) {
118+
throw new Error(`The argument name "${definition.argumentName}" contains an invalid character "${match[0]}".`
119+
+ ` Only upper-case letters, numbers, and underscores are allowed.`);
120+
}
92121
this.argumentName = definition.argumentName;
93122
}
94123
}

libraries/ts-command-line/src/CommandLineParameterProvider.ts

Lines changed: 47 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -59,110 +59,117 @@ export abstract class CommandLineParameterProvider {
5959
}
6060

6161
/**
62-
* Defines a command-line switch whose boolean value is true if the switch is provided,
63-
* and false otherwise.
62+
* Defines a command-line parameter whose value must be a string from a fixed set of
63+
* allowable choices (similar to an enum).
6464
*
6565
* @remarks
66-
* Example: example-tool --debug
66+
* Example: example-tool --log-level warn
6767
*/
68-
public defineFlagParameter(definition: ICommandLineFlagDefinition): CommandLineFlagParameter {
69-
const parameter: CommandLineFlagParameter = new CommandLineFlagParameter(definition);
68+
public defineChoiceParameter(definition: ICommandLineChoiceDefinition): CommandLineChoiceParameter {
69+
const parameter: CommandLineChoiceParameter = new CommandLineChoiceParameter(definition);
7070
this._defineParameter(parameter);
7171
return parameter;
7272
}
7373

7474
/**
75-
* Returns the CommandLineFlagParameter with the specified long name.
75+
* Returns the CommandLineChoiceParameter with the specified long name.
7676
* @remarks
7777
* This method throws an exception if the parameter is not defined.
7878
*/
79-
public getFlagParameter(parameterLongName: string): CommandLineFlagParameter {
80-
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.Flag);
79+
public getChoiceParameter(parameterLongName: string): CommandLineChoiceParameter {
80+
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.Choice);
8181
}
8282

8383
/**
84-
* Defines a command-line parameter whose value is a single text string.
84+
* Defines a command-line switch whose boolean value is true if the switch is provided,
85+
* and false otherwise.
8586
*
8687
* @remarks
87-
* Example: example-tool --message "Hello, world!"
88+
* Example: example-tool --debug
8889
*/
89-
public defineStringParameter(definition: ICommandLineStringDefinition): CommandLineStringParameter {
90-
const parameter: CommandLineStringParameter = new CommandLineStringParameter(definition);
90+
public defineFlagParameter(definition: ICommandLineFlagDefinition): CommandLineFlagParameter {
91+
const parameter: CommandLineFlagParameter = new CommandLineFlagParameter(definition);
9192
this._defineParameter(parameter);
9293
return parameter;
9394
}
9495

9596
/**
96-
* Returns the CommandLineStringParameter with the specified long name.
97+
* Returns the CommandLineFlagParameter with the specified long name.
9798
* @remarks
9899
* This method throws an exception if the parameter is not defined.
99100
*/
100-
public getStringParameter(parameterLongName: string): CommandLineStringParameter {
101-
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.String);
101+
public getFlagParameter(parameterLongName: string): CommandLineFlagParameter {
102+
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.Flag);
102103
}
103104

104105
/**
105-
* Defines a command-line parameter whose value is one or more text strings.
106+
* Defines a command-line parameter whose value is an integer.
106107
*
107108
* @remarks
108-
* Example: example-tool --add file1.txt --add file2.txt --add file3.txt
109+
* Example: example-tool l --max-attempts 5
109110
*/
110-
public defineStringListParameter(definition: ICommandLineStringListDefinition): CommandLineStringListParameter {
111-
const parameter: CommandLineStringListParameter = new CommandLineStringListParameter(definition);
111+
public defineIntegerParameter(definition: ICommandLineIntegerDefinition): CommandLineIntegerParameter {
112+
const parameter: CommandLineIntegerParameter = new CommandLineIntegerParameter(definition);
112113
this._defineParameter(parameter);
113114
return parameter;
114115
}
115116

116117
/**
117-
* Returns the CommandLineStringListParameter with the specified long name.
118+
* Returns the CommandLineIntegerParameter with the specified long name.
118119
* @remarks
119120
* This method throws an exception if the parameter is not defined.
120121
*/
121-
public getStringListParameter(parameterLongName: string): CommandLineStringListParameter {
122-
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.StringList);
122+
public getIntegerParameter(parameterLongName: string): CommandLineIntegerParameter {
123+
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.Integer);
123124
}
124125

125126
/**
126-
* Defines a command-line parameter whose value is an integer.
127+
* Defines a command-line parameter whose value is a single text string.
127128
*
128129
* @remarks
129-
* Example: example-tool l --max-attempts 5
130+
* Example: example-tool --message "Hello, world!"
130131
*/
131-
public defineIntegerParameter(definition: ICommandLineIntegerDefinition): CommandLineIntegerParameter {
132-
const parameter: CommandLineIntegerParameter = new CommandLineIntegerParameter(definition);
132+
public defineStringParameter(definition: ICommandLineStringDefinition): CommandLineStringParameter {
133+
const parameter: CommandLineStringParameter = new CommandLineStringParameter(definition);
133134
this._defineParameter(parameter);
134135
return parameter;
135136
}
136137

137138
/**
138-
* Returns the CommandLineIntegerParameter with the specified long name.
139+
* Returns the CommandLineStringParameter with the specified long name.
139140
* @remarks
140141
* This method throws an exception if the parameter is not defined.
141142
*/
142-
public getIntegerParameter(parameterLongName: string): CommandLineIntegerParameter {
143-
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.Integer);
143+
public getStringParameter(parameterLongName: string): CommandLineStringParameter {
144+
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.String);
144145
}
145146

146147
/**
147-
* Defines a command-line parameter whose value must be a string from a fixed set of
148-
* allowable choices (similar to an enum).
148+
* Defines a command-line parameter whose value is one or more text strings.
149149
*
150150
* @remarks
151-
* Example: example-tool --log-level warn
151+
* Example: example-tool --add file1.txt --add file2.txt --add file3.txt
152152
*/
153-
public defineChoiceParameter(definition: ICommandLineChoiceDefinition): CommandLineChoiceParameter {
154-
const parameter: CommandLineChoiceParameter = new CommandLineChoiceParameter(definition);
153+
public defineStringListParameter(definition: ICommandLineStringListDefinition): CommandLineStringListParameter {
154+
const parameter: CommandLineStringListParameter = new CommandLineStringListParameter(definition);
155155
this._defineParameter(parameter);
156156
return parameter;
157157
}
158158

159159
/**
160-
* Returns the CommandLineChoiceParameter with the specified long name.
160+
* Returns the CommandLineStringListParameter with the specified long name.
161161
* @remarks
162162
* This method throws an exception if the parameter is not defined.
163163
*/
164-
public getChoiceParameter(parameterLongName: string): CommandLineChoiceParameter {
165-
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.Choice);
164+
public getStringListParameter(parameterLongName: string): CommandLineStringListParameter {
165+
return this._getFlagParameter(parameterLongName, CommandLineParameterKind.StringList);
166+
}
167+
168+
/**
169+
* Generates the command-line help text.
170+
*/
171+
public renderHelpText(): string {
172+
return this._argumentParser.formatHelp();
166173
}
167174

168175
/**
@@ -222,12 +229,12 @@ export abstract class CommandLineParameterProvider {
222229
case CommandLineParameterKind.Flag:
223230
argparseOptions.action = 'storeTrue';
224231
break;
225-
case CommandLineParameterKind.StringList:
226-
argparseOptions.action = 'append';
227-
break;
228232
case CommandLineParameterKind.Integer:
229233
argparseOptions.type = 'int';
230234
break;
235+
case CommandLineParameterKind.StringList:
236+
argparseOptions.action = 'append';
237+
break;
231238
}
232239

233240
this._argumentParser.addArgument(names, argparseOptions);

libraries/ts-command-line/src/CommandLineParser.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,9 +76,9 @@ export abstract class CommandLineParser extends CommandLineParameterProvider {
7676
/**
7777
* Defines a new action that can be used with the CommandLineParser instance.
7878
*/
79-
public addAction(command: CommandLineAction): void {
80-
command._buildParser(this._actionsSubParser);
81-
this._actions.push(command);
79+
public addAction(action: CommandLineAction): void {
80+
action._buildParser(this._actionsSubParser);
81+
this._actions.push(action);
8282
}
8383

8484
/**
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
2+
// See LICENSE in the project root for license information.
3+
4+
import * as colors from 'colors';
5+
6+
import { DynamicCommandLineParser } from '../DynamicCommandLineParser';
7+
import { DynamicCommandLineAction } from '../DynamicCommandLineAction';
8+
9+
describe('CommandLineParameter', () => {
10+
const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser(
11+
{
12+
toolFilename: 'example',
13+
toolDescription: 'An example project'
14+
}
15+
);
16+
commandLineParser.defineFlagParameter({
17+
parameterLongName: '--global-flag',
18+
parameterShortName: '-g',
19+
description: 'A flag that affects all actions'
20+
});
21+
22+
const action: DynamicCommandLineAction = new DynamicCommandLineAction({
23+
actionVerb: 'do-job',
24+
summary: 'does the job',
25+
documentation: 'a longer description'
26+
});
27+
commandLineParser.addAction(action);
28+
29+
action.defineChoiceParameter({
30+
parameterLongName: '--choice',
31+
parameterShortName: '-c',
32+
description: 'A choice',
33+
alternatives: [ 'one', 'two' ],
34+
defaultValue: 'one'
35+
});
36+
action.defineFlagParameter({
37+
parameterLongName: '--flag',
38+
parameterShortName: '-f',
39+
description: 'A flag'
40+
});
41+
action.defineIntegerParameter({
42+
parameterLongName: '--integer',
43+
parameterShortName: '-i',
44+
description: 'An integer',
45+
argumentName: 'NUMBER'
46+
});
47+
action.defineStringParameter({
48+
parameterLongName: '--string',
49+
parameterShortName: '-s',
50+
description: 'A string',
51+
argumentName: 'TEXT'
52+
});
53+
action.defineStringListParameter({
54+
parameterLongName: '--string-list',
55+
parameterShortName: '-l',
56+
description: 'A string list',
57+
argumentName: 'LIST'
58+
});
59+
60+
it('prints the global help', () => {
61+
const helpText: string = colors.stripColors(commandLineParser.renderHelpText());
62+
expect(helpText).toMatchSnapshot();
63+
});
64+
65+
it('prints the action help', () => {
66+
const helpText: string = colors.stripColors(action.renderHelpText());
67+
expect(helpText).toMatchSnapshot();
68+
});
69+
});

libraries/ts-command-line/src/test/CommandLineParser.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,9 @@ class TestCommandLine extends CommandLineParser {
4646
}
4747
}
4848

49-
describe('CommandLineParser tests', () => {
49+
describe('CommandLineParser', () => {
5050

51-
it('simple case', () => {
51+
it('executes an action', () => {
5252
const commandLineParser: TestCommandLine = new TestCommandLine();
5353

5454
return commandLineParser.execute(['do-job', '--flag']).then(() => {

libraries/ts-command-line/src/test/DynamicCommandLineParser.test.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,9 @@ import { DynamicCommandLineParser } from '../DynamicCommandLineParser';
55
import { DynamicCommandLineAction } from '../DynamicCommandLineAction';
66
import { CommandLineFlagParameter } from '../CommandLineParameter';
77

8-
describe('DynamicCommandLineParser tests', () => {
8+
describe('DynamicCommandLineParser', () => {
99

10-
it('simple case', () => {
10+
it('parses an action', () => {
1111
const commandLineParser: DynamicCommandLineParser = new DynamicCommandLineParser(
1212
{
1313
toolFilename: 'example',
@@ -31,7 +31,6 @@ describe('DynamicCommandLineParser tests', () => {
3131

3232
const retrievedParameter: CommandLineFlagParameter = action.getFlagParameter('--flag');
3333
expect(retrievedParameter.value).toBe(true);
34-
3534
});
3635
});
3736
});

0 commit comments

Comments
 (0)