Refactor CliApplication.RunAsync using chain of responsibility

This commit is contained in:
Alexey Golub
2019-08-25 14:54:29 +03:00
parent 3f7c02342d
commit cd3892bf83

View File

@@ -43,6 +43,140 @@ namespace CliFx
_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 />
public async Task<int> RunAsync(IReadOnlyList<string> commandLineArguments)
{
@@ -53,111 +187,19 @@ namespace CliFx
// Parse command input from arguments
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
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
var targetCommandSchema = availableCommandSchemas.FindByName(commandInput.CommandName);
// Handle cases where requested command is not defined
if (targetCommandSchema == null)
{
var isError = false;
// If specified a command - show error
if (commandInput.IsCommandSpecified())
{
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;
// Chain handlers until the first one that produces an exit code
return
await HandleDebugDirectiveAsync(commandInput) ??
HandlePreviewDirective(commandInput) ??
HandleVersionOption(commandInput) ??
HandleHelpOption(commandInput, availableCommandSchemas, targetCommandSchema) ??
await HandleCommandExecutionAsync(commandInput, targetCommandSchema);
}
catch (Exception ex)
{