mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-14 16:02:50 +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:
parent
e7ce6a69b7
commit
a94bc15746
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user