This repository has been archived by the owner on Nov 13, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 31
/
CommandYargs.ts
276 lines (264 loc) · 14.3 KB
/
CommandYargs.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
/*
* This program and the accompanying materials are made available under the terms of the
* Eclipse Public License v2.0 which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-v20.html
*
* SPDX-License-Identifier: EPL-2.0
*
* Copyright Contributors to the Zowe Project.
*
*/
import { Arguments, Argv, Options } from "yargs";
import { isNullOrUndefined } from "util";
import { Constants } from "../../../constants";
import { IYargsResponse } from "./doc/IYargsResponse";
import { AbstractCommandYargs, YargsCommandCompleted } from "./AbstractCommandYargs";
import { ICommandOptionDefinition } from "../../src/doc/option/ICommandOptionDefinition";
import { ICommandDefinition } from "../doc/ICommandDefinition";
import { CommandProcessor } from "../CommandProcessor";
import { ICommandResponse } from "../../src/doc/response/response/ICommandResponse";
import { CommandResponse } from "../../src/response/CommandResponse";
/**
* Define an Imperative Command to Yargs. A command implies that an implementation is present (differs from a "group")
* and it does not have children.
*/
export class CommandYargs extends AbstractCommandYargs {
/**
* Define the options to Yargs.
* @param {yargs.Argv} yargsInstance: The instance of yargs to define the options.
* @param {ICommandOptionDefinition[]} brightOptions: The option definition document array.
*/
public static defineOptionsToYargs(yargsInstance: Argv, brightOptions: ICommandOptionDefinition[]): void {
if (!isNullOrUndefined(brightOptions)) {
for (const option of brightOptions) {
const definition: Options = {
alias: option.aliases,
description: option.description
};
if (!isNullOrUndefined(option.type)) {
// don't let yargs handle any types that we are validating ourselves
// and don't use custom types as the yargs type since yargs won't understand
if (option.type !== "number" &&
option.type !== "json") {
definition.type = option.type as any;
} else if (option.type === "json") {
definition.type = "string";
}
}
// If this is a boolean type option, default it to undefined so that we can distinguish
// between the user not specifying the option at all and them specifying =false
if (option.type === "boolean") {
definition.default = undefined;
}
yargsInstance.option(option.name, definition);
}
}
}
/**
* Define the Imperative JSON command definition to Yargs. Once the command is defined, Yargs will parse and
* invoke its 'handler' (below). The handler can then invoke the potential "chain" of handlers in sequence and
* is notified when they complete via a promise.
* @param {YargsCommandCompleted} commandExecuted - Callback invoked when a command execution is complete.
*/
public defineCommandToYargs(commandExecuted: YargsCommandCompleted) {
// TODO: Fix this when we re-implement Experimental Features - always enabled for now
// if (!this.definition.experimental || (ExperimentalFeatures.ENABLED)) {
// console.log("experimental? " + this.definition.experimental)
this.log.debug("Defining command: " + this.definition.name);
/**
* Define the command to Yargs.
*/
this.yargs.command(
/**
* Define the command name, plus and positional arguments and aliases.
*/
[this.definition.name + this.buildPositionalString()].concat(this.definition.aliases),
/**
* Define the description to yargs.
*/
this.definition.description,
/**
* Define the options to Yargs.
* @param {yargs.Argv} argsForBuilder: The yargs instance to define the options
* @return {yargs.Argv}: The populated instance.
*/
(argsForBuilder: Argv) => {
this.log.debug("Defining command builder for: " + this.definition.name);
argsForBuilder.strict();
CommandYargs.defineOptionsToYargs(argsForBuilder, this.definition.options);
return argsForBuilder;
},
/**
* Define the handler for the command. Invoked when Yargs matches the input command to this definition.
* If help is present, then we will invoke the help handler for the command definition.
* @param {yargs.Argv} argsForHandler: The yargs instance with the specified command line options.
*/
async (argsForHandler: Arguments) => {
this.log.debug("Handler invoked for: " + this.definition.name);
/**
* If help is present, invoke the help for this command definition.
*/
if (argsForHandler[Constants.HELP_OPTION] || argsForHandler[Constants.HELP_EXAMPLES]) {
this.log.debug("Executing help: " + this.definition.name);
this.executeHelp(argsForHandler, commandExecuted);
} else if (argsForHandler[Constants.HELP_WEB_OPTION]) {
this.log.debug("Executing web help: " + this.definition.name);
this.executeWebHelp(argsForHandler, commandExecuted);
} else {
this.log.debug("Executing Handlers: " + this.definition.name);
/**
* Before invoking the "command" we will build the complete set of handlers for the command.
* In some cases, "providers" may be present along the chain of definitions and must be invoked
* in sequence.
*/
const handlerDefinition: any[] = [];
for (const parent of this.parents) {
const definition: any = parent.definition;
if (!isNullOrUndefined(definition.handler)) {
handlerDefinition.push(definition);
}
}
handlerDefinition.push(this.definition);
/**
* If handlers are present, invoke the set in sequence OR fail with an error - the "command"
*/
if (handlerDefinition.length > 0) {
this.log.debug("Executing Handlers (%s total)", handlerDefinition.length);
const responses: ICommandResponse[] = [];
/**
* Invoke all handlers and collect all responses.
*/
this.invokeHandlers(handlerDefinition, 0, argsForHandler, responses)
.then((successResponses) => {
commandExecuted(argsForHandler, this.getBrightYargsResponse(true,
`${successResponses.length} command handlers invoked.`,
"command handler invoked", successResponses));
})
.catch((errorResponses) => {
const response: IYargsResponse = this.getBrightYargsResponse(false,
`Error in command ${this.definition.name}`,
"command handler invoked", errorResponses);
this.log.error(`Error in command ${this.definition.name}`);
this.log.error(require("util").inspect(errorResponses));
commandExecuted(argsForHandler, response);
});
} else {
/**
* No handlers were present - Respond with an error - this condition should not occur if the
* definition was validated against the schema.
*/
const response: IYargsResponse = this.getBrightYargsResponse(false,
`No handlers provided for ${this.definition.name}`,
"command handler invoked");
commandExecuted(argsForHandler, response);
}
}
});
// } else {
// this.log.debug("Experimental command %s disabled due to user settings", this.definition.name);
// }
}
/**
* Construct the positional argument string for Yargs. The positional arguments are always constructed as
* "optional" to yargs. This prevents yargs from "throwing errors" if the user requests --help and the positional
* parameters are not specified.
* @return {string}: The positional argument string used in the Yargs command definition.
*/
private buildPositionalString(): string {
if (this.definition.positionals) {
this.log.debug("Building positional string from: " + this.definition.name);
let yargPositionalSyntax: string = (this.definition.positionals.length > 0) ? " " : "";
this.definition.positionals.forEach((positional) => {
yargPositionalSyntax += ("[" + positional.name + "] ");
});
const posString: string = yargPositionalSyntax.substr(0, yargPositionalSyntax.lastIndexOf(" "));
this.log.debug("Positional String: " + posString);
return posString;
} else {
return "";
}
}
/**
* Invoke the "chain" of command handlers provided for this definition.
* @param {ICommandDefinition[]} handlers: The array of handlers to invoke.
* @param {number} index: The current index in the handler array.
* @param {yargs.Argv} argsForHandler: The arguments passed on the command line for the handler.
* @param {ICommandResponse[]} responses: The collection of responses from each handler.
* @return {Promise<ICommandResponse[]>}: The promise to be fulfilled when all handlers are complete.
*/
private invokeHandlers(handlers: ICommandDefinition[], index: number, argsForHandler: Arguments,
responses: ICommandResponse[]): Promise<ICommandResponse[]> {
return new Promise<ICommandResponse[]>((invokeHandlersResponse, invokeHandlersError) => {
/**
* If the index is greater than the handler array, fulfill the promise.
*/
if (index > handlers.length - 1) {
invokeHandlersResponse(responses);
} else {
// TODO: figure out a better way to handle the fact that yargs keeps going after fail()
if (!AbstractCommandYargs.STOP_YARGS) {
// Determine if we should print JSON
const printJson: boolean = (index === handlers.length - 1) &&
(argsForHandler[Constants.JSON_OPTION] as boolean);
// Protect against issues allocating the command processor
try {
new CommandProcessor({
definition: handlers[index],
fullDefinition: this.constructDefinitionTree(),
helpGenerator: this.helpGeneratorFactory.getHelpGenerator({
commandDefinition: handlers[index],
fullCommandTree: this.constructDefinitionTree(),
experimentalCommandsDescription: this.yargsParms.experimentalCommandDescription
}),
profileManagerFactory: this.profileManagerFactory,
rootCommandName: this.rootCommandName,
commandLine: this.commandLine,
envVariablePrefix: this.envVariablePrefix,
promptPhrase: this.promptPhrase
}).invoke({
arguments: argsForHandler,
silent: false,
responseFormat: (printJson) ? "json" : "default"
}).then((commandHandlerResponse) => {
/**
* Push the responses - If an error occurs, reject the promise with the error response.
*/
responses.push(commandHandlerResponse);
if (!commandHandlerResponse.success) {
invokeHandlersError(responses);
} else {
/**
* Re-invoke with the next index.
*/
return this.invokeHandlers(handlers, ++index, argsForHandler, responses)
.then((recursiveResponses) => {
invokeHandlersResponse(recursiveResponses);
})
.catch((recursiveError) => {
invokeHandlersError(recursiveError);
});
}
}).catch((error) => {
invokeHandlersError(error);
});
} catch (processorError) {
const response = new CommandResponse({
silent: false,
responseFormat: (printJson) ? "json" : "default"
});
response.failed();
response.console.errorHeader("Internal Command Error");
response.console.error(processorError.message);
response.setError({
msg: processorError.message
});
if (response.responseFormat === "json") {
response.writeJsonResponse();
}
invokeHandlersError(processorError);
}
}
}
});
}
}