mirror of
				https://github.com/nsnail/spectre.console.git
				synced 2025-11-04 10:35: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.
 | 
					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
 | 
					## 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).
 | 
					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));
 | 
					            throw new ArgumentNullException(nameof(configurator));
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        configurator.Settings.Interceptor = interceptor;
 | 
					        configurator.Settings.Registrar.RegisterInstance<ICommandInterceptor>(interceptor);
 | 
				
			||||||
        return configurator;
 | 
					        return configurator;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -50,6 +50,7 @@ public interface ICommandAppSettings
 | 
				
			|||||||
    /// Gets or sets the <see cref="ICommandInterceptor"/> used
 | 
					    /// Gets or sets the <see cref="ICommandInterceptor"/> used
 | 
				
			||||||
    /// to intercept settings before it's being sent to the command.
 | 
					    /// to intercept settings before it's being sent to the command.
 | 
				
			||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
 | 
					    [Obsolete("Register the interceptor with the ITypeRegistrar.")]
 | 
				
			||||||
    ICommandInterceptor? Interceptor { get; set; }
 | 
					    ICommandInterceptor? Interceptor { get; set; }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    /// <summary>
 | 
					    /// <summary>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -12,5 +12,25 @@ public interface ICommandInterceptor
 | 
				
			|||||||
    /// </summary>
 | 
					    /// </summary>
 | 
				
			||||||
    /// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
 | 
					    /// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
 | 
				
			||||||
    /// <param name="settings">The intercepted <see cref="CommandSettings"/>.</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());
 | 
					            VersionHelper.GetVersion(Assembly.GetEntryAssembly());
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private static Task<int> Execute(
 | 
					    private static async Task<int> Execute(
 | 
				
			||||||
        CommandTree leaf,
 | 
					        CommandTree leaf,
 | 
				
			||||||
        CommandTree tree,
 | 
					        CommandTree tree,
 | 
				
			||||||
        CommandContext context,
 | 
					        CommandContext context,
 | 
				
			||||||
@@ -130,7 +130,19 @@ internal sealed class CommandExecutor
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        // Bind the command tree against the settings.
 | 
					        // Bind the command tree against the settings.
 | 
				
			||||||
        var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
 | 
					        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.
 | 
					        // Create and validate the command.
 | 
				
			||||||
        var command = leaf.CreateCommand(resolver);
 | 
					        var command = leaf.CreateCommand(resolver);
 | 
				
			||||||
@@ -141,6 +153,12 @@ internal sealed class CommandExecutor
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // Execute the command.
 | 
					        // 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 int MaximumIndirectExamples { get; set; }
 | 
				
			||||||
    public bool ShowOptionDefaultValues { get; set; }
 | 
					    public bool ShowOptionDefaultValues { get; set; }
 | 
				
			||||||
    public IAnsiConsole? Console { get; set; }
 | 
					    public IAnsiConsole? Console { get; set; }
 | 
				
			||||||
 | 
					    [Obsolete("Register the interceptor with the ITypeRegistrar.")]
 | 
				
			||||||
    public ICommandInterceptor? Interceptor { get; set; }
 | 
					    public ICommandInterceptor? Interceptor { get; set; }
 | 
				
			||||||
    public ITypeRegistrarFrontend Registrar { get; set; }
 | 
					    public ITypeRegistrarFrontend Registrar { get; set; }
 | 
				
			||||||
    public CaseSensitivity CaseSensitivity { get; set; }
 | 
					    public CaseSensitivity CaseSensitivity { get; set; }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,4 +21,11 @@ public sealed class CallbackCommandInterceptor : ICommandInterceptor
 | 
				
			|||||||
    {
 | 
					    {
 | 
				
			||||||
        _callback(context, settings);
 | 
					        _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