Initial commit
This commit is contained in:
268
node_modules/@rushstack/ts-command-line/lib/providers/CommandLineParser.js
generated
vendored
Normal file
268
node_modules/@rushstack/ts-command-line/lib/providers/CommandLineParser.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
"use strict";
|
||||
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
|
||||
// See LICENSE in the project root for license information.
|
||||
Object.defineProperty(exports, "__esModule", { value: true });
|
||||
exports.CommandLineParser = void 0;
|
||||
const terminal_1 = require("@rushstack/terminal");
|
||||
const CommandLineParameterProvider_1 = require("./CommandLineParameterProvider");
|
||||
const CommandLineParserExitError_1 = require("./CommandLineParserExitError");
|
||||
const TabCompletionAction_1 = require("./TabCompletionAction");
|
||||
const TypeUuidLite_1 = require("../TypeUuidLite");
|
||||
const escapeSprintf_1 = require("../escapeSprintf");
|
||||
/**
|
||||
* The "argparse" library is a relatively advanced command-line parser with features such
|
||||
* as word-wrapping and intelligible error messages (that are lacking in other similar
|
||||
* libraries such as commander, yargs, and nomnom). Unfortunately, its ruby-inspired API
|
||||
* is awkward to use. The abstract base classes CommandLineParser and CommandLineAction
|
||||
* provide a wrapper for "argparse" that makes defining and consuming arguments quick
|
||||
* and simple, and enforces that appropriate documentation is provided for each parameter.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
class CommandLineParser extends CommandLineParameterProvider_1.CommandLineParameterProvider {
|
||||
constructor(options) {
|
||||
super();
|
||||
this._executed = false;
|
||||
this._tabCompleteActionWasAdded = false;
|
||||
this._options = options;
|
||||
this._actions = [];
|
||||
this._actionsByName = new Map();
|
||||
const { toolFilename, toolDescription, toolEpilog } = options;
|
||||
this._argumentParser = new CommandLineParserExitError_1.CustomArgumentParser({
|
||||
addHelp: true,
|
||||
prog: toolFilename,
|
||||
description: (0, escapeSprintf_1.escapeSprintf)(toolDescription),
|
||||
epilog: terminal_1.Colorize.bold((0, escapeSprintf_1.escapeSprintf)(toolEpilog !== null && toolEpilog !== void 0 ? toolEpilog : `For detailed help about a specific command, use: ${toolFilename} <command> -h`))
|
||||
});
|
||||
}
|
||||
/**
|
||||
* Returns the list of actions that were defined for this CommandLineParser object.
|
||||
*/
|
||||
get actions() {
|
||||
return this._actions;
|
||||
}
|
||||
/**
|
||||
* Defines a new action that can be used with the CommandLineParser instance.
|
||||
*/
|
||||
addAction(action) {
|
||||
if (!this._actionsSubParser) {
|
||||
this._actionsSubParser = this._argumentParser.addSubparsers({
|
||||
metavar: '<command>',
|
||||
dest: 'action'
|
||||
});
|
||||
}
|
||||
action._buildParser(this._actionsSubParser);
|
||||
this._actions.push(action);
|
||||
this._actionsByName.set(action.actionName, action);
|
||||
}
|
||||
/**
|
||||
* Retrieves the action with the specified name. If no matching action is found,
|
||||
* an exception is thrown.
|
||||
*/
|
||||
getAction(actionName) {
|
||||
const action = this.tryGetAction(actionName);
|
||||
if (!action) {
|
||||
throw new Error(`The action "${actionName}" was not defined`);
|
||||
}
|
||||
return action;
|
||||
}
|
||||
/**
|
||||
* Retrieves the action with the specified name. If no matching action is found,
|
||||
* undefined is returned.
|
||||
*/
|
||||
tryGetAction(actionName) {
|
||||
return this._actionsByName.get(actionName);
|
||||
}
|
||||
/**
|
||||
* The program entry point will call this method to begin parsing command-line arguments
|
||||
* and executing the corresponding action.
|
||||
*
|
||||
* @remarks
|
||||
* The returned promise will never reject: If an error occurs, it will be printed
|
||||
* to stderr, process.exitCode will be set to 1, and the promise will resolve to false.
|
||||
* This simplifies the most common usage scenario where the program entry point doesn't
|
||||
* want to be involved with the command-line logic, and will discard the promise without
|
||||
* a then() or catch() block.
|
||||
*
|
||||
* If your caller wants to trap and handle errors, use {@link CommandLineParser.executeWithoutErrorHandlingAsync}
|
||||
* instead.
|
||||
*
|
||||
* @param args - the command-line arguments to be parsed; if omitted, then
|
||||
* the process.argv will be used
|
||||
*/
|
||||
async executeAsync(args) {
|
||||
if (this._options.enableTabCompletionAction && !this._tabCompleteActionWasAdded) {
|
||||
this.addAction(new TabCompletionAction_1.TabCompleteAction(this.actions, this.parameters));
|
||||
this._tabCompleteActionWasAdded = true;
|
||||
}
|
||||
try {
|
||||
await this.executeWithoutErrorHandlingAsync(args);
|
||||
return true;
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof CommandLineParserExitError_1.CommandLineParserExitError) {
|
||||
// executeWithoutErrorHandlingAsync() handles the successful cases,
|
||||
// so here we can assume err has a nonzero exit code
|
||||
if (err.message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(err.message);
|
||||
}
|
||||
if (!process.exitCode) {
|
||||
process.exitCode = err.exitCode;
|
||||
}
|
||||
}
|
||||
else if (TypeUuidLite_1.TypeUuid.isInstanceOf(err, TypeUuidLite_1.uuidAlreadyReportedError)) {
|
||||
// AlreadyReportedError
|
||||
if (!process.exitCode) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
let message = (err.message || 'An unknown error occurred').trim();
|
||||
// If the message doesn't already start with "Error:" then add a prefix
|
||||
if (!/^(error|internal error|warning)\b/i.test(message)) {
|
||||
message = 'Error: ' + message;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.error();
|
||||
// eslint-disable-next-line no-console
|
||||
console.error(terminal_1.Colorize.red(message));
|
||||
if (!process.exitCode) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* This is similar to {@link CommandLineParser.executeAsync}, except that execution errors
|
||||
* simply cause the promise to reject. It is the caller's responsibility to trap
|
||||
*/
|
||||
async executeWithoutErrorHandlingAsync(args) {
|
||||
var _a, _b;
|
||||
try {
|
||||
if (this._executed) {
|
||||
// In the future we could allow the same parser to be invoked multiple times
|
||||
// with different arguments. We'll do that work as soon as someone encounters
|
||||
// a real world need for it.
|
||||
throw new Error('executeAsync() was already called for this parser instance');
|
||||
}
|
||||
this._executed = true;
|
||||
this._validateDefinitions();
|
||||
// Register the parameters before we print help or parse the CLI
|
||||
const initialState = {
|
||||
parentParameterNames: new Set()
|
||||
};
|
||||
this._registerDefinedParameters(initialState);
|
||||
if (!args) {
|
||||
// 0=node.exe, 1=script name
|
||||
args = process.argv.slice(2);
|
||||
}
|
||||
if (this.actions.length > 0) {
|
||||
if (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;
|
||||
}
|
||||
// Alias actions may provide a list of default params to add after the action name.
|
||||
// Since we don't know which params are required and which are optional, perform a
|
||||
// manual search for the action name to obtain the default params and insert them if
|
||||
// any are found. We will guess that the action name is the first arg that doesn't
|
||||
// start with a hyphen.
|
||||
const actionNameIndex = args.findIndex((x) => !x.startsWith('-'));
|
||||
if (actionNameIndex !== undefined) {
|
||||
const actionName = args[actionNameIndex];
|
||||
const action = this.tryGetAction(actionName);
|
||||
const aliasAction = action;
|
||||
if ((_a = aliasAction === null || aliasAction === void 0 ? void 0 : aliasAction.defaultParameters) === null || _a === void 0 ? void 0 : _a.length) {
|
||||
const insertIndex = actionNameIndex + 1;
|
||||
args = args.slice(0, insertIndex).concat(aliasAction.defaultParameters, args.slice(insertIndex));
|
||||
}
|
||||
}
|
||||
}
|
||||
const postParse = () => {
|
||||
this._postParse();
|
||||
for (const action of this.actions) {
|
||||
action._postParse();
|
||||
}
|
||||
};
|
||||
function patchFormatUsageForArgumentParser(argumentParser) {
|
||||
const originalFormatUsage = argumentParser.formatUsage.bind(argumentParser);
|
||||
argumentParser.formatUsage = () => {
|
||||
postParse();
|
||||
return originalFormatUsage();
|
||||
};
|
||||
}
|
||||
this._preParse();
|
||||
patchFormatUsageForArgumentParser(this._argumentParser);
|
||||
for (const action of this.actions) {
|
||||
action._preParse();
|
||||
patchFormatUsageForArgumentParser(action._getArgumentParser());
|
||||
}
|
||||
const data = this._argumentParser.parseArgs(args);
|
||||
postParse();
|
||||
this._processParsedData(this._options, data);
|
||||
this.selectedAction = this.tryGetAction(data.action);
|
||||
if (this.actions.length > 0 && !this.selectedAction) {
|
||||
const actions = this.actions.map((x) => x.actionName);
|
||||
throw new Error(`An action must be specified (${actions.join(', ')})`);
|
||||
}
|
||||
(_b = this.selectedAction) === null || _b === void 0 ? void 0 : _b._processParsedData(this._options, data);
|
||||
await this.onExecuteAsync();
|
||||
}
|
||||
catch (err) {
|
||||
if (err instanceof CommandLineParserExitError_1.CommandLineParserExitError) {
|
||||
if (!err.exitCode) {
|
||||
// non-error exit modeled using exception handling
|
||||
if (err.message) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(err.message);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
/** @internal */
|
||||
_registerDefinedParameters(state) {
|
||||
super._registerDefinedParameters(state);
|
||||
const { parentParameterNames } = state;
|
||||
const updatedParentParameterNames = new Set([
|
||||
...parentParameterNames,
|
||||
...this._registeredParameterParserKeysByName.keys()
|
||||
]);
|
||||
const parentState = {
|
||||
...state,
|
||||
parentParameterNames: updatedParentParameterNames
|
||||
};
|
||||
for (const action of this._actions) {
|
||||
action._registerDefinedParameters(parentState);
|
||||
}
|
||||
}
|
||||
_validateDefinitions() {
|
||||
if (this.remainder && this.actions.length > 0) {
|
||||
// This is apparently not supported by argparse
|
||||
throw new Error('defineCommandLineRemainder() cannot be called for a CommandLineParser with actions');
|
||||
}
|
||||
}
|
||||
/**
|
||||
* {@inheritDoc CommandLineParameterProvider._getArgumentParser}
|
||||
* @internal
|
||||
*/
|
||||
_getArgumentParser() {
|
||||
return this._argumentParser;
|
||||
}
|
||||
/**
|
||||
* This hook allows the subclass to perform additional operations before or after
|
||||
* the chosen action is executed.
|
||||
*/
|
||||
async onExecuteAsync() {
|
||||
if (this.selectedAction) {
|
||||
await this.selectedAction._executeAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
exports.CommandLineParser = CommandLineParser;
|
||||
//# sourceMappingURL=CommandLineParser.js.map
|
||||
Reference in New Issue
Block a user