From cd3892bf83357f6cd56f1accaa9f4c3901cf891f Mon Sep 17 00:00:00 2001 From: Alexey Golub Date: Sun, 25 Aug 2019 14:54:29 +0300 Subject: [PATCH] Refactor CliApplication.RunAsync using chain of responsibility --- CliFx/CliApplication.cs | 240 +++++++++++++++++++++++----------------- 1 file changed, 141 insertions(+), 99 deletions(-) diff --git a/CliFx/CliApplication.cs b/CliFx/CliApplication.cs index b2e80ad..02d3b42 100644 --- a/CliFx/CliApplication.cs +++ b/CliFx/CliApplication.cs @@ -43,6 +43,140 @@ namespace CliFx _helpTextRenderer = helpTextRenderer.GuardNotNull(nameof(helpTextRenderer)); } + private async Task 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 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 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; + } + /// public async Task RunAsync(IReadOnlyList 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) {