mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-01 01:25:27 +08:00 
			
		
		
		
	Add the possibility to register multiple interceptors (#1412)
Having the interceptors registered with the ITypeRegistrar also enables the usage of ITypeResolver in interceptors.
This commit is contained in:
		| @@ -81,9 +81,12 @@ Hint: If you do write your own implementation of `TypeRegistrar` and `TypeResolv | ||||
| there is a utility `TypeRegistrarBaseTests` available that can be used to ensure your implementations adhere to the required implementation. Simply call `TypeRegistrarBaseTests.RunAllTests()` and expect no `TypeRegistrarBaseTests.TestFailedException` to be thrown. | ||||
|  | ||||
| ## Interception | ||||
| Interceptors can be registered with the `TypeRegistrar` (or with a custom DI-Container). Alternatively, `CommandApp` also provides a `SetInterceptor` configuration. | ||||
|  | ||||
| `CommandApp` also provides a `SetInterceptor` configuration. An interceptor is run before all commands are executed. This is typically used for configuring logging or other infrastructure concerns. | ||||
| All interceptors must implement `ICommandInterceptor`. Upon execution of a command, The `Intercept`-Method of an instance of your interceptor will be called with the parsed settings. This provides an opportunity for configuring any infrastructure or modifying the settings. | ||||
| When the command has been run, the `InterceptResult`-Method of the same instance is called with the result of the command. | ||||
| This provides an opportunity to modify the result and also to tear down any infrastructure in use. | ||||
|  | ||||
| All interceptors must implement `ICommandInterceptor`. Upon execution of a command, an instance of your interceptor will be called with the parsed settings. This provides an opportunity for configuring any infrastructure or modifying the settings. | ||||
| The `Intercept`-Method of each interceptor is run before the command is executed and the `InterceptResult`-Method is run after it. These are typically used for configuring logging or other infrastructure concerns. | ||||
|  | ||||
| For an example of using the interceptor to configure logging, see the [Serilog demo](https://github.com/spectreconsole/spectre.console/tree/main/examples/Cli/Logging). | ||||
|   | ||||
| @@ -229,7 +229,7 @@ public static class ConfiguratorExtensions | ||||
|             throw new ArgumentNullException(nameof(configurator)); | ||||
|         } | ||||
|  | ||||
|         configurator.Settings.Interceptor = interceptor; | ||||
|         configurator.Settings.Registrar.RegisterInstance<ICommandInterceptor>(interceptor); | ||||
|         return configurator; | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -50,6 +50,7 @@ public interface ICommandAppSettings | ||||
|     /// Gets or sets the <see cref="ICommandInterceptor"/> used | ||||
|     /// to intercept settings before it's being sent to the command. | ||||
|     /// </summary> | ||||
|     [Obsolete("Register the interceptor with the ITypeRegistrar.")] | ||||
|     ICommandInterceptor? Interceptor { get; set; } | ||||
|  | ||||
|     /// <summary> | ||||
|   | ||||
| @@ -12,5 +12,25 @@ public interface ICommandInterceptor | ||||
|     /// </summary> | ||||
|     /// <param name="context">The intercepted <see cref="CommandContext"/>.</param> | ||||
|     /// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param> | ||||
|     void Intercept(CommandContext context, CommandSettings settings); | ||||
|     void Intercept(CommandContext context, CommandSettings settings) | ||||
| #if NETSTANDARD2_0 | ||||
|     ; | ||||
| #else | ||||
|     { | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     /// <summary> | ||||
|     /// Intercepts a command result before it's passed as the result. | ||||
|     /// </summary> | ||||
|     /// <param name="context">The intercepted <see cref="CommandContext"/>.</param> | ||||
|     /// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param> | ||||
|     /// <param name="result">The result from the command execution.</param> | ||||
|     void InterceptResult(CommandContext context, CommandSettings settings, ref int result) | ||||
| #if NETSTANDARD2_0 | ||||
|     ; | ||||
| #else | ||||
|     { | ||||
|     } | ||||
| #endif | ||||
| } | ||||
| @@ -121,7 +121,7 @@ internal sealed class CommandExecutor | ||||
|             VersionHelper.GetVersion(Assembly.GetEntryAssembly()); | ||||
|     } | ||||
|  | ||||
|     private static Task<int> Execute( | ||||
|     private static async Task<int> Execute( | ||||
|         CommandTree leaf, | ||||
|         CommandTree tree, | ||||
|         CommandContext context, | ||||
| @@ -130,7 +130,19 @@ internal sealed class CommandExecutor | ||||
|     { | ||||
|         // Bind the command tree against the settings. | ||||
|         var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver); | ||||
|         configuration.Settings.Interceptor?.Intercept(context, settings); | ||||
|         var interceptors = | ||||
|             ((IEnumerable<ICommandInterceptor>?)resolver.Resolve(typeof(IEnumerable<ICommandInterceptor>)) | ||||
|             ?? Array.Empty<ICommandInterceptor>()).ToList(); | ||||
| #pragma warning disable CS0618 // Type or member is obsolete | ||||
|         if (configuration.Settings.Interceptor != null) | ||||
|         { | ||||
|             interceptors.Add(configuration.Settings.Interceptor); | ||||
|         } | ||||
| #pragma warning restore CS0618 // Type or member is obsolete | ||||
|         foreach (var interceptor in interceptors) | ||||
|         { | ||||
|             interceptor.Intercept(context, settings); | ||||
|         } | ||||
|  | ||||
|         // Create and validate the command. | ||||
|         var command = leaf.CreateCommand(resolver); | ||||
| @@ -141,6 +153,12 @@ internal sealed class CommandExecutor | ||||
|         } | ||||
|  | ||||
|         // Execute the command. | ||||
|         return command.Execute(context, settings); | ||||
|         var result = await command.Execute(context, settings); | ||||
|         foreach (var interceptor in interceptors) | ||||
|         { | ||||
|             interceptor.InterceptResult(context, settings, ref result); | ||||
|         } | ||||
|  | ||||
|         return result; | ||||
|     } | ||||
| } | ||||
| @@ -8,6 +8,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings | ||||
|     public int MaximumIndirectExamples { get; set; } | ||||
|     public bool ShowOptionDefaultValues { get; set; } | ||||
|     public IAnsiConsole? Console { get; set; } | ||||
|     [Obsolete("Register the interceptor with the ITypeRegistrar.")] | ||||
|     public ICommandInterceptor? Interceptor { get; set; } | ||||
|     public ITypeRegistrarFrontend Registrar { get; set; } | ||||
|     public CaseSensitivity CaseSensitivity { get; set; } | ||||
|   | ||||
| @@ -21,4 +21,11 @@ public sealed class CallbackCommandInterceptor : ICommandInterceptor | ||||
|     { | ||||
|         _callback(context, settings); | ||||
|     } | ||||
|  | ||||
| #if NETSTANDARD2_0 | ||||
|     /// <inheritdoc/> | ||||
|     public void InterceptResult(CommandContext context, CommandSettings settings, ref int result) | ||||
|     { | ||||
|     } | ||||
| #endif | ||||
| } | ||||
| @@ -0,0 +1,90 @@ | ||||
| namespace Spectre.Console.Tests.Unit.Cli; | ||||
|  | ||||
| public sealed partial class CommandAppTests | ||||
| { | ||||
|     public sealed class Interceptor | ||||
|     { | ||||
|         public sealed class NoCommand : Command<NoCommand.Settings> | ||||
|         { | ||||
|             public sealed class Settings : CommandSettings | ||||
|             { | ||||
|             } | ||||
|  | ||||
|             public override int Execute(CommandContext context, Settings settings) | ||||
|             { | ||||
|                 return 0; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public sealed class MyInterceptor : ICommandInterceptor | ||||
|         { | ||||
|             private readonly Action<CommandContext, CommandSettings> _action; | ||||
|  | ||||
|             public MyInterceptor(Action<CommandContext, CommandSettings> action) | ||||
|             { | ||||
|                 _action = action; | ||||
|             } | ||||
|  | ||||
|             public void Intercept(CommandContext context, CommandSettings settings) | ||||
|             { | ||||
|                 _action(context, settings); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         public sealed class MyResultInterceptor : ICommandInterceptor | ||||
|         { | ||||
|             private readonly Func<CommandContext, CommandSettings, int, int> _function; | ||||
|  | ||||
|             public MyResultInterceptor(Func<CommandContext, CommandSettings, int, int> function) | ||||
|             { | ||||
|                 _function = function; | ||||
|             } | ||||
|  | ||||
|             public void InterceptResult(CommandContext context, CommandSettings settings, ref int result) | ||||
|             { | ||||
|                 result = _function(context, settings, result); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Run_The_Interceptor() | ||||
|         { | ||||
|             // Given | ||||
|             var count = 0; | ||||
|             var app = new CommandApp<NoCommand>(); | ||||
|             var interceptor = new MyInterceptor((_, _) => | ||||
|             { | ||||
|                 count += 1; | ||||
|             }); | ||||
|             app.Configure(config => config.SetInterceptor(interceptor)); | ||||
|  | ||||
|             // When | ||||
|             app.Run(Array.Empty<string>()); | ||||
|  | ||||
|             // Then | ||||
|             count.ShouldBe(1); // to be sure | ||||
|         } | ||||
|  | ||||
|         [Fact] | ||||
|         public void Should_Run_The_ResultInterceptor() | ||||
|         { | ||||
|             // Given | ||||
|             var count = 0; | ||||
|             const int Expected = 123; | ||||
|             var app = new CommandApp<NoCommand>(); | ||||
|             var interceptor = new MyResultInterceptor((_, _, _) => | ||||
|             { | ||||
|                 count += 1; | ||||
|                 return Expected; | ||||
|             }); | ||||
|             app.Configure(config => config.SetInterceptor(interceptor)); | ||||
|  | ||||
|             // When | ||||
|             var actual = app.Run(Array.Empty<string>()); | ||||
|  | ||||
|             // Then | ||||
|             count.ShouldBe(1); | ||||
|             actual.ShouldBe(Expected); | ||||
|         } | ||||
|     } | ||||
| } | ||||
		Reference in New Issue
	
	Block a user
	 Nils Andresen
					Nils Andresen