mirror of
https://github.com/Tyrrrz/CliFx.git
synced 2025-10-25 15:19:17 +00:00
Refactor CliApplication.RunAsync using chain of responsibility
This commit is contained in:
@@ -43,6 +43,140 @@ namespace CliFx
|
|||||||
_helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer));
|
_helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task<int?> HandleDebugDirectiveAsync(CommandInput commandInput)
|
||||||
|
{
|
||||||
|
// Debug mode is enabled if it's allowed in the application and it was requested via corresponding directive
|
||||||
|
var isDebugMode = _configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified();
|
||||||
|
|
||||||
|
// If not in debug mode, pass execution to the next handler
|
||||||
|
if (!isDebugMode)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Inform user which process they need to attach debugger to
|
||||||
|
_console.WithForegroundColor(ConsoleColor.Green,
|
||||||
|
() => _console.Output.WriteLine($"Attach debugger to PID {Process.GetCurrentProcess().Id} to continue."));
|
||||||
|
|
||||||
|
// Wait until debugger is attached
|
||||||
|
while (!Debugger.IsAttached)
|
||||||
|
await Task.Delay(100);
|
||||||
|
|
||||||
|
// Debug directive never short-circuits
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? HandlePreviewDirective(CommandInput commandInput)
|
||||||
|
{
|
||||||
|
// Preview mode is enabled if it's allowed in the application and it was requested via corresponding directive
|
||||||
|
var isPreviewMode = _configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified();
|
||||||
|
|
||||||
|
// If not in preview mode, pass execution to the next handler
|
||||||
|
if (!isPreviewMode)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Render command name
|
||||||
|
_console.Output.WriteLine($"Command name: {commandInput.CommandName}");
|
||||||
|
_console.Output.WriteLine();
|
||||||
|
|
||||||
|
// Render directives
|
||||||
|
_console.Output.WriteLine("Directives:");
|
||||||
|
foreach (var directive in commandInput.Directives)
|
||||||
|
{
|
||||||
|
_console.Output.Write(" ");
|
||||||
|
_console.Output.WriteLine(directive);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Margin
|
||||||
|
_console.Output.WriteLine();
|
||||||
|
|
||||||
|
// Render options
|
||||||
|
_console.Output.WriteLine("Options:");
|
||||||
|
foreach (var option in commandInput.Options)
|
||||||
|
{
|
||||||
|
_console.Output.Write(" ");
|
||||||
|
_console.Output.WriteLine(option);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Short-circuit with exit code 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? HandleVersionOption(CommandInput commandInput)
|
||||||
|
{
|
||||||
|
// Version should be rendered if it was requested on a default command
|
||||||
|
var shouldRenderVersion = !commandInput.IsCommandSpecified() && commandInput.IsVersionOptionSpecified();
|
||||||
|
|
||||||
|
// If shouldn't render version, pass execution to the next handler
|
||||||
|
if (!shouldRenderVersion)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Render version text
|
||||||
|
_console.Output.WriteLine(_metadata.VersionText);
|
||||||
|
|
||||||
|
// Short-circuit with exit code 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int? HandleHelpOption(CommandInput commandInput,
|
||||||
|
IReadOnlyList<CommandSchema> availableCommandSchemas, CommandSchema targetCommandSchema)
|
||||||
|
{
|
||||||
|
// Help should be rendered if it was requested, or when executing a command which isn't defined
|
||||||
|
var shouldRenderHelp = commandInput.IsHelpOptionSpecified() || targetCommandSchema == null;
|
||||||
|
|
||||||
|
// If shouldn't render help, pass execution to the next handler
|
||||||
|
if (!shouldRenderHelp)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
// Keep track whether there was an error in the input
|
||||||
|
var isError = false;
|
||||||
|
|
||||||
|
// If target command isn't defined, find its parent
|
||||||
|
if (targetCommandSchema == null)
|
||||||
|
{
|
||||||
|
// If command was specified, inform the user that it's not defined
|
||||||
|
if (commandInput.IsCommandSpecified())
|
||||||
|
{
|
||||||
|
_console.WithForegroundColor(ConsoleColor.Red,
|
||||||
|
() => _console.Error.WriteLine($"Specified command [{commandInput.CommandName}] is not defined."));
|
||||||
|
|
||||||
|
isError = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace target command with closest parent of specified command
|
||||||
|
targetCommandSchema = availableCommandSchemas.FindParent(commandInput.CommandName);
|
||||||
|
|
||||||
|
// If there's no parent, replace with stub default command
|
||||||
|
if (targetCommandSchema == null)
|
||||||
|
{
|
||||||
|
targetCommandSchema = CommandSchema.StubDefaultCommand;
|
||||||
|
availableCommandSchemas = availableCommandSchemas.Concat(CommandSchema.StubDefaultCommand).ToArray();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build help text source
|
||||||
|
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, targetCommandSchema);
|
||||||
|
|
||||||
|
// Render help text
|
||||||
|
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
||||||
|
|
||||||
|
// Short-circuit with appropriate exit code
|
||||||
|
return isError ? -1 : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task<int> HandleCommandExecutionAsync(CommandInput commandInput, CommandSchema targetCommandSchema)
|
||||||
|
{
|
||||||
|
// Create an instance of the command
|
||||||
|
var command = _commandFactory.CreateCommand(targetCommandSchema);
|
||||||
|
|
||||||
|
// Populate command with options according to its schema
|
||||||
|
_commandInitializer.InitializeCommand(command, targetCommandSchema, commandInput);
|
||||||
|
|
||||||
|
// Execute command
|
||||||
|
await command.ExecuteAsync(_console);
|
||||||
|
|
||||||
|
// Finish the chain with exit code 0
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
|
||||||
{
|
{
|
||||||
@@ -53,111 +187,19 @@ namespace CliFx
|
|||||||
// Parse command input from arguments
|
// Parse command input from arguments
|
||||||
var commandInput = _commandInputParser.ParseCommandInput(commandLineArguments);
|
var commandInput = _commandInputParser.ParseCommandInput(commandLineArguments);
|
||||||
|
|
||||||
// Wait for debugger to be attached if debug directive was specified
|
|
||||||
if (_configuration.IsDebugModeAllowed && commandInput.IsDebugDirectiveSpecified())
|
|
||||||
{
|
|
||||||
// Whoever comes up with an idea on how to cover this in tests is a genius
|
|
||||||
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Green,
|
|
||||||
() => _console.Output.WriteLine($"Attach debugger to PID {Process.GetCurrentProcess().Id} to continue."));
|
|
||||||
|
|
||||||
while (!Debugger.IsAttached)
|
|
||||||
await Task.Delay(100);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show parsed arguments if preview directive was specified
|
|
||||||
if (_configuration.IsPreviewModeAllowed && commandInput.IsPreviewDirectiveSpecified())
|
|
||||||
{
|
|
||||||
_console.Output.WriteLine($"Command name: {commandInput.CommandName}");
|
|
||||||
_console.Output.WriteLine();
|
|
||||||
|
|
||||||
_console.Output.WriteLine("Directives:");
|
|
||||||
foreach (var directive in commandInput.Directives)
|
|
||||||
{
|
|
||||||
_console.Output.Write(" ");
|
|
||||||
_console.Output.WriteLine(directive);
|
|
||||||
}
|
|
||||||
_console.Output.WriteLine();
|
|
||||||
|
|
||||||
_console.Output.WriteLine("Options:");
|
|
||||||
foreach (var option in commandInput.Options)
|
|
||||||
{
|
|
||||||
_console.Output.Write(" ");
|
|
||||||
_console.Output.WriteLine(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get schemas for all available command types
|
// Get schemas for all available command types
|
||||||
var availableCommandSchemas = _commandSchemaResolver.GetCommandSchemas(_configuration.CommandTypes);
|
var availableCommandSchemas = _commandSchemaResolver.GetCommandSchemas(_configuration.CommandTypes);
|
||||||
|
|
||||||
// Show version if version option was specified and command was not specified (only works on default command)
|
|
||||||
if (commandInput.IsVersionOptionSpecified() && !commandInput.IsCommandSpecified())
|
|
||||||
{
|
|
||||||
_console.Output.WriteLine(_metadata.VersionText);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find command schema matching the name specified in the input
|
// Find command schema matching the name specified in the input
|
||||||
var targetCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
|
var targetCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
|
||||||
|
|
||||||
// Handle cases where requested command is not defined
|
// Chain handlers until the first one that produces an exit code
|
||||||
if (targetCommandSchema == null)
|
return
|
||||||
{
|
await HandleDebugDirectiveAsync(commandInput) ??
|
||||||
var isError = false;
|
HandlePreviewDirective(commandInput) ??
|
||||||
|
HandleVersionOption(commandInput) ??
|
||||||
// If specified a command - show error
|
HandleHelpOption(commandInput, availableCommandSchemas, targetCommandSchema) ??
|
||||||
if (commandInput.IsCommandSpecified())
|
await HandleCommandExecutionAsync(commandInput, targetCommandSchema);
|
||||||
{
|
|
||||||
isError = true;
|
|
||||||
|
|
||||||
_console.WithForegroundColor(ConsoleColor.Red,
|
|
||||||
() => _console.Error.WriteLine($"Specified command [{commandInput.CommandName}] is not defined."));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Get parent command schema
|
|
||||||
var parentCommandSchema = availableCommandSchemas.FindParent(commandInput.CommandName);
|
|
||||||
|
|
||||||
// Show help for parent command if it's defined
|
|
||||||
if (parentCommandSchema != null)
|
|
||||||
{
|
|
||||||
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, parentCommandSchema);
|
|
||||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
|
||||||
}
|
|
||||||
// Otherwise show help for a stub default command
|
|
||||||
else
|
|
||||||
{
|
|
||||||
var helpTextSource = new HelpTextSource(_metadata,
|
|
||||||
availableCommandSchemas.Concat(CommandSchema.StubDefaultCommand).ToArray(),
|
|
||||||
CommandSchema.StubDefaultCommand);
|
|
||||||
|
|
||||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
return isError ? -1 : 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Show help if help option was specified
|
|
||||||
if (commandInput.IsHelpOptionSpecified())
|
|
||||||
{
|
|
||||||
var helpTextSource = new HelpTextSource(_metadata, availableCommandSchemas, targetCommandSchema);
|
|
||||||
_helpTextRenderer.RenderHelpText(_console, helpTextSource);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create an instance of the command
|
|
||||||
var command = _commandFactory.CreateCommand(targetCommandSchema);
|
|
||||||
|
|
||||||
// Populate command with options according to its schema
|
|
||||||
_commandInitializer.InitializeCommand(command, targetCommandSchema, commandInput);
|
|
||||||
|
|
||||||
// Execute command
|
|
||||||
await command.ExecuteAsync(_console);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user