mirror of
https://github.com/nsnail/spectre.console.git
synced 2025-04-16 08:52: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.
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user