Skip to content

Commit 15339f5

Browse files
committed
Add the possibility to register multiple interceptors
Having the interceptors registered with the ITypeRegistrar also enables the usage of ITypeResolver in interceptors.
1 parent c62f79e commit 15339f5

File tree

7 files changed

+142
-5
lines changed

7 files changed

+142
-5
lines changed

src/Spectre.Console.Cli/ConfiguratorExtensions.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,7 +229,7 @@ public static IConfigurator SetInterceptor(this IConfigurator configurator, ICom
229229
throw new ArgumentNullException(nameof(configurator));
230230
}
231231

232-
configurator.Settings.Interceptor = interceptor;
232+
configurator.Settings.Registrar.RegisterInstance<ICommandInterceptor>(interceptor);
233233
return configurator;
234234
}
235235

src/Spectre.Console.Cli/ICommandAppSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@ public interface ICommandAppSettings
5050
/// Gets or sets the <see cref="ICommandInterceptor"/> used
5151
/// to intercept settings before it's being sent to the command.
5252
/// </summary>
53+
[Obsolete("Register the interceptor with the ITypeRegistrar.")]
5354
ICommandInterceptor? Interceptor { get; set; }
5455

5556
/// <summary>

src/Spectre.Console.Cli/ICommandInterceptor.cs

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,25 @@ public interface ICommandInterceptor
1212
/// </summary>
1313
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
1414
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
15-
void Intercept(CommandContext context, CommandSettings settings);
15+
void Intercept(CommandContext context, CommandSettings settings)
16+
#if NETSTANDARD2_0
17+
;
18+
#else
19+
{
20+
}
21+
#endif
22+
23+
/// <summary>
24+
/// Intercepts a command result before it's passed as the result.
25+
/// </summary>
26+
/// <param name="context">The intercepted <see cref="CommandContext"/>.</param>
27+
/// <param name="settings">The intercepted <see cref="CommandSettings"/>.</param>
28+
/// <param name="result">The result from the command execution.</param>
29+
void InterceptResult(CommandContext context, CommandSettings settings, ref int result)
30+
#if NETSTANDARD2_0
31+
;
32+
#else
33+
{
34+
}
35+
#endif
1636
}

src/Spectre.Console.Cli/Internal/CommandExecutor.cs

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ private static string ResolveApplicationVersion(IConfiguration configuration)
121121
VersionHelper.GetVersion(Assembly.GetEntryAssembly());
122122
}
123123

124-
private static Task<int> Execute(
124+
private static async Task<int> Execute(
125125
CommandTree leaf,
126126
CommandTree tree,
127127
CommandContext context,
@@ -130,7 +130,19 @@ private static Task<int> Execute(
130130
{
131131
// Bind the command tree against the settings.
132132
var settings = CommandBinder.Bind(tree, leaf.Command.SettingsType, resolver);
133-
configuration.Settings.Interceptor?.Intercept(context, settings);
133+
var interceptors =
134+
((IEnumerable<ICommandInterceptor>?)resolver.Resolve(typeof(IEnumerable<ICommandInterceptor>))
135+
?? Array.Empty<ICommandInterceptor>()).ToList();
136+
#pragma warning disable CS0618 // Type or member is obsolete
137+
if (configuration.Settings.Interceptor != null)
138+
{
139+
interceptors.Add(configuration.Settings.Interceptor);
140+
}
141+
#pragma warning restore CS0618 // Type or member is obsolete
142+
foreach (var interceptor in interceptors)
143+
{
144+
interceptor.Intercept(context, settings);
145+
}
134146

135147
// Create and validate the command.
136148
var command = leaf.CreateCommand(resolver);
@@ -141,6 +153,12 @@ private static Task<int> Execute(
141153
}
142154

143155
// Execute the command.
144-
return command.Execute(context, settings);
156+
var result = await command.Execute(context, settings);
157+
foreach (var interceptor in interceptors)
158+
{
159+
interceptor.InterceptResult(context, settings, ref result);
160+
}
161+
162+
return result;
145163
}
146164
}

src/Spectre.Console.Cli/Internal/Configuration/CommandAppSettings.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings
88
public int MaximumIndirectExamples { get; set; }
99
public bool ShowOptionDefaultValues { get; set; }
1010
public IAnsiConsole? Console { get; set; }
11+
[Obsolete("Register the interceptor with the ITypeRegistrar.")]
1112
public ICommandInterceptor? Interceptor { get; set; }
1213
public ITypeRegistrarFrontend Registrar { get; set; }
1314
public CaseSensitivity CaseSensitivity { get; set; }

src/Spectre.Console.Testing/Cli/CallbackCommandInterceptor.cs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,11 @@ public void Intercept(CommandContext context, CommandSettings settings)
2121
{
2222
_callback(context, settings);
2323
}
24+
25+
#if NETSTANDARD2_0
26+
/// <inheritdoc/>
27+
public void InterceptResult(CommandContext context, CommandSettings settings, ref int result)
28+
{
29+
}
30+
#endif
2431
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
namespace Spectre.Console.Tests.Unit.Cli;
2+
3+
public sealed partial class CommandAppTests
4+
{
5+
public sealed class Interceptor
6+
{
7+
public sealed class NoCommand : Command<NoCommand.Settings>
8+
{
9+
public sealed class Settings : CommandSettings
10+
{
11+
}
12+
13+
public override int Execute(CommandContext context, Settings settings)
14+
{
15+
return 0;
16+
}
17+
}
18+
19+
public sealed class MyInterceptor : ICommandInterceptor
20+
{
21+
private readonly Action<CommandContext, CommandSettings> _action;
22+
23+
public MyInterceptor(Action<CommandContext, CommandSettings> action)
24+
{
25+
_action = action;
26+
}
27+
28+
public void Intercept(CommandContext context, CommandSettings settings)
29+
{
30+
_action(context, settings);
31+
}
32+
}
33+
34+
public sealed class MyResultInterceptor : ICommandInterceptor
35+
{
36+
private readonly Func<CommandContext, CommandSettings, int, int> _function;
37+
38+
public MyResultInterceptor(Func<CommandContext, CommandSettings, int, int> function)
39+
{
40+
_function = function;
41+
}
42+
43+
public void InterceptResult(CommandContext context, CommandSettings settings, ref int result)
44+
{
45+
result = _function(context, settings, result);
46+
}
47+
}
48+
49+
[Fact]
50+
public void Should_Run_The_Interceptor()
51+
{
52+
// Given
53+
var count = 0;
54+
var app = new CommandApp<NoCommand>();
55+
var interceptor = new MyInterceptor((_, _) =>
56+
{
57+
count += 1;
58+
});
59+
app.Configure(config => config.SetInterceptor(interceptor));
60+
61+
// When
62+
app.Run(Array.Empty<string>());
63+
64+
// Then
65+
count.ShouldBe(1); // to be sure
66+
}
67+
68+
[Fact]
69+
public void Should_Run_The_ResultInterceptor()
70+
{
71+
// Given
72+
var count = 0;
73+
const int Expected = 123;
74+
var app = new CommandApp<NoCommand>();
75+
var interceptor = new MyResultInterceptor((_, _, _) =>
76+
{
77+
count += 1;
78+
return Expected;
79+
});
80+
app.Configure(config => config.SetInterceptor(interceptor));
81+
82+
// When
83+
var actual = app.Run(Array.Empty<string>());
84+
85+
// Then
86+
count.ShouldBe(1);
87+
actual.ShouldBe(Expected);
88+
}
89+
}
90+
}

0 commit comments

Comments
 (0)