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));
|
||||
}
|
||||
|
||||
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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user