Skip to content

Commit

Permalink
Add top-level CancellationToken support
Browse files Browse the repository at this point in the history
  • Loading branch information
0xced committed Sep 11, 2024
1 parent c70a8b8 commit 57113a6
Show file tree
Hide file tree
Showing 25 changed files with 183 additions and 58 deletions.
11 changes: 6 additions & 5 deletions src/Spectre.Console.Cli/AsyncCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,19 +9,20 @@ public abstract class AsyncCommand : ICommand<EmptyCommandSettings>
/// Executes the command.
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context);
public abstract Task<int> ExecuteAsync(CommandContext context, CancellationToken cancellationToken);

/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings, CancellationToken cancellationToken)
{
return ExecuteAsync(context);
return ExecuteAsync(context, cancellationToken);
}

/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings, CancellationToken cancellationToken)
{
return ExecuteAsync(context);
return ExecuteAsync(context, cancellationToken);
}

/// <inheritdoc/>
Expand Down
11 changes: 6 additions & 5 deletions src/Spectre.Console.Cli/AsyncCommandOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,9 @@ public virtual ValidationResult Validate(CommandContext context, TSettings setti
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings);
public abstract Task<int> ExecuteAsync(CommandContext context, TSettings settings, CancellationToken cancellationToken);

/// <inheritdoc/>
ValidationResult ICommand.Validate(CommandContext context, CommandSettings settings)
Expand All @@ -33,15 +34,15 @@ ValidationResult ICommand.Validate(CommandContext context, CommandSettings setti
}

/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings, CancellationToken cancellationToken)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return ExecuteAsync(context, (TSettings)settings);
return ExecuteAsync(context, (TSettings)settings, cancellationToken);
}

/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings, CancellationToken cancellationToken)
{
return ExecuteAsync(context, settings);
return ExecuteAsync(context, settings, cancellationToken);
}
}
4 changes: 2 additions & 2 deletions src/Spectre.Console.Cli/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,13 @@ public abstract class Command : ICommand<EmptyCommandSettings>
public abstract int Execute(CommandContext context);

/// <inheritdoc/>
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings)
Task<int> ICommand<EmptyCommandSettings>.Execute(CommandContext context, EmptyCommandSettings settings, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(context));
}

/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(context));
}
Expand Down
21 changes: 9 additions & 12 deletions src/Spectre.Console.Cli/CommandApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,22 +48,14 @@ public DefaultCommandConfigurator SetDefaultCommand<TCommand>()
return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>());
}

/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
/// <inheritdoc/>
public int Run(IEnumerable<string> args)
{
return RunAsync(args).GetAwaiter().GetResult();
}

/// <summary>
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <returns>The exit code from the executed command.</returns>
public async Task<int> RunAsync(IEnumerable<string> args)
/// <inheritdoc/>
public async Task<int> RunAsync(IEnumerable<string> args, CancellationToken cancellationToken = default)
{
try
{
Expand All @@ -82,7 +74,7 @@ public async Task<int> RunAsync(IEnumerable<string> args)
}

return await _executor
.Execute(_configurator, args)
.Execute(_configurator, args, cancellationToken)
.ConfigureAwait(false);
}
catch (Exception ex)
Expand All @@ -105,6 +97,11 @@ public async Task<int> RunAsync(IEnumerable<string> args)
return _configurator.Settings.ExceptionHandler(ex, null);
}

if (ex is OperationCanceledException)
{
return _configurator.Settings.CancellationExitCode;
}

// Render the exception.
var pretty = GetRenderableErrorMessage(ex);
if (pretty != null)
Expand Down
5 changes: 3 additions & 2 deletions src/Spectre.Console.Cli/CommandAppOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,10 +45,11 @@ public int Run(IEnumerable<string> args)
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the application.</param>
/// <returns>The exit code from the executed command.</returns>
public Task<int> RunAsync(IEnumerable<string> args)
public Task<int> RunAsync(IEnumerable<string> args, CancellationToken cancellationToken = default)
{
return _app.RunAsync(args);
return _app.RunAsync(args, cancellationToken);
}

internal Configurator GetConfigurator()
Expand Down
4 changes: 2 additions & 2 deletions src/Spectre.Console.Cli/CommandOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,14 +34,14 @@ ValidationResult ICommand.Validate(CommandContext context, CommandSettings setti
}

/// <inheritdoc/>
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings)
Task<int> ICommand.Execute(CommandContext context, CommandSettings settings, CancellationToken cancellationToken)
{
Debug.Assert(settings is TSettings, "Command settings is of unexpected type.");
return Task.FromResult(Execute(context, (TSettings)settings));
}

/// <inheritdoc/>
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings)
Task<int> ICommand<TSettings>.Execute(CommandContext context, TSettings settings, CancellationToken cancellationToken)
{
return Task.FromResult(Execute(context, settings));
}
Expand Down
24 changes: 21 additions & 3 deletions src/Spectre.Console.Cli/ConfiguratorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,24 @@ public static IConfigurator PropagateExceptions(this IConfigurator configurator)
return configurator;
}

/// <summary>
/// Tells the command line application to return the specified exit code when it's aborted through the <see cref="CancellationToken"/>.
/// The default cancellation exit code is 130.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="exitCode">The exit code to return in case of cancellation.</param>
/// <returns>A configurator that can be used to configure the application further.</returns>
public static IConfigurator CancellationExitCode(this IConfigurator configurator, int exitCode)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}

configurator.Settings.CancellationExitCode = exitCode;
return configurator;
}

/// <summary>
/// Configures case sensitivity.
/// </summary>
Expand Down Expand Up @@ -331,7 +349,7 @@ public static ICommandConfigurator AddAsyncDelegate(
throw new ArgumentNullException(nameof(configurator));
}

return configurator.AddAsyncDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
return configurator.AddAsyncDelegate<EmptyCommandSettings>(name, (c, _, _) => func(c));
}

/// <summary>
Expand Down Expand Up @@ -372,15 +390,15 @@ public static ICommandConfigurator AddDelegate<TSettings>(
public static ICommandConfigurator AddAsyncDelegate<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Func<CommandContext, Task<int>> func)
Func<CommandContext, CancellationToken, Task<int>> func)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}

return configurator.AddAsyncDelegate<TSettings>(name, (c, _) => func(c));
return configurator.AddAsyncDelegate<TSettings>(name, (c, _, ct) => func(c, ct));
}

/// <summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Spectre.Console.Cli/ICommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public interface ICommand
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param>
/// <returns>The validation result.</returns>
Task<int> Execute(CommandContext context, CommandSettings settings);
Task<int> Execute(CommandContext context, CommandSettings settings, CancellationToken cancellationToken);
}
3 changes: 2 additions & 1 deletion src/Spectre.Console.Cli/ICommandApp.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ public interface ICommandApp
/// Runs the command line application with specified arguments.
/// </summary>
/// <param name="args">The arguments.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the application.</param>
/// <returns>The exit code from the executed command.</returns>
Task<int> RunAsync(IEnumerable<string> args);
Task<int> RunAsync(IEnumerable<string> args, CancellationToken cancellationToken = default);
}
6 changes: 6 additions & 0 deletions src/Spectre.Console.Cli/ICommandAppSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,12 @@ public interface ICommandAppSettings
/// </summary>
bool PropagateExceptions { get; set; }

/// <summary>
/// Gets or sets the value used as the application exit code when it's aborted through the <see cref="CancellationToken"/>.
/// The default cancellation exit code is 130.
/// </summary>
int CancellationExitCode { get; set; }

/// <summary>
/// Gets or sets a value indicating whether or not examples should be validated.
/// </summary>
Expand Down
3 changes: 2 additions & 1 deletion src/Spectre.Console.Cli/ICommandOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public interface ICommand<TSettings> : ICommandLimiter<TSettings>
/// </summary>
/// <param name="context">The command context.</param>
/// <param name="settings">The settings.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to abort the command.</param>
/// <returns>An integer indicating whether or not the command executed successfully.</returns>
Task<int> Execute(CommandContext context, TSettings settings);
Task<int> Execute(CommandContext context, TSettings settings, CancellationToken cancellationToken = default);
}
10 changes: 10 additions & 0 deletions src/Spectre.Console.Cli/IConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,16 @@ ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TS
ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, Task<int>> func)
where TSettings : CommandSettings;

/// <summary>
/// Adds a command that executes an async delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate, with cancellation support, to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, CancellationToken, Task<int>> func)
where TSettings : CommandSettings;

/// <summary>
/// Adds a command branch.
/// </summary>
Expand Down
10 changes: 10 additions & 0 deletions src/Spectre.Console.Cli/IConfiguratorOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,16 @@ ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandCont
ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, Task<int>> func)
where TDerivedSettings : TSettings;

/// <summary>
/// Adds a command that executes an async delegate.
/// </summary>
/// <typeparam name="TDerivedSettings">The derived command setting type.</typeparam>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate, with cancellation support, to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, CancellationToken, Task<int>> func)
where TDerivedSettings : TSettings;

/// <summary>
/// Adds a command branch.
/// </summary>
Expand Down
9 changes: 5 additions & 4 deletions src/Spectre.Console.Cli/Internal/CommandExecutor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public CommandExecutor(ITypeRegistrar registrar)
_registrar.Register(typeof(DefaultPairDeconstructor), typeof(DefaultPairDeconstructor));
}

public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args)
public async Task<int> Execute(IConfiguration configuration, IEnumerable<string> args, CancellationToken cancellationToken)
{
if (configuration == null)
{
Expand Down Expand Up @@ -97,7 +97,7 @@ public async Task<int> Execute(IConfiguration configuration, IEnumerable<string>
leaf.Command.Data);

// Execute the command tree.
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration).ConfigureAwait(false);
return await Execute(leaf, parsedResult.Tree, context, resolver, configuration, cancellationToken).ConfigureAwait(false);
}
}

Expand Down Expand Up @@ -134,7 +134,8 @@ private static async Task<int> Execute(
CommandTree tree,
CommandContext context,
ITypeResolver resolver,
IConfiguration configuration)
IConfiguration configuration,
CancellationToken cancellationToken)
{
try
{
Expand Down Expand Up @@ -163,7 +164,7 @@ private static async Task<int> Execute(
}

// Execute the command.
var result = await command.Execute(context, settings);
var result = await command.Execute(context, settings, cancellationToken);
foreach (var interceptor in interceptors)
{
interceptor.InterceptResult(context, settings, ref result);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ internal sealed class CommandAppSettings : ICommandAppSettings
public HelpProviderStyle? HelpProviderStyles { get; set; }
public bool StrictParsing { get; set; }
public bool ConvertFlagsToRemainingArguments { get; set; }
public int CancellationExitCode { get; set; }

public ParsingMode ParsingMode =>
StrictParsing ? ParsingMode.Strict : ParsingMode.Relaxed;
Expand All @@ -33,6 +34,7 @@ public CommandAppSettings(ITypeRegistrar registrar)
TrimTrailingPeriod = true;
HelpProviderStyles = HelpProviderStyle.Default;
ConvertFlagsToRemainingArguments = false;
CancellationExitCode = 130;
}

public bool IsTrue(Func<CommandAppSettings, bool> func, string environmentVariableName)
Expand Down
12 changes: 10 additions & 2 deletions src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -57,15 +57,23 @@ public ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandCont
where TSettings : CommandSettings
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>(
name, (context, settings) => Task.FromResult(func(context, (TSettings)settings))));
name, (context, settings, _) => Task.FromResult(func(context, (TSettings)settings))));
return new CommandConfigurator(command);
}

public ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, Task<int>> func)
where TSettings : CommandSettings
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>(
name, (context, settings) => func(context, (TSettings)settings)));
name, (context, settings, _) => func(context, (TSettings)settings)));
return new CommandConfigurator(command);
}

public ICommandConfigurator AddAsyncDelegate<TSettings>(string name, Func<CommandContext, TSettings, CancellationToken, Task<int>> func)
where TSettings : CommandSettings
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>(
name, (context, settings, cancellationToken) => func(context, (TSettings)settings, cancellationToken)));
return new CommandConfigurator(command);
}

Expand Down
14 changes: 12 additions & 2 deletions src/Spectre.Console.Cli/Internal/Configuration/ConfiguratorOfT.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<Comm
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
name, (context, settings) => Task.FromResult(func(context, (TDerivedSettings)settings)));
name, (context, settings, _) => Task.FromResult(func(context, (TDerivedSettings)settings)));

_command.Children.Add(command);
return new CommandConfigurator(command);
Expand All @@ -60,7 +60,17 @@ public ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
name, (context, settings) => func(context, (TDerivedSettings)settings));
name, (context, settings, _) => func(context, (TDerivedSettings)settings));

_command.Children.Add(command);
return new CommandConfigurator(command);
}

public ICommandConfigurator AddAsyncDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, CancellationToken, Task<int>> func)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromDelegate<TDerivedSettings>(
name, (context, settings, cancellationToken) => func(context, (TDerivedSettings)settings, cancellationToken));

_command.Children.Add(command);
return new CommandConfigurator(command);
Expand Down
Loading

0 comments on commit 57113a6

Please sign in to comment.