Skip to content

Commit

Permalink
Implemented AddAsyncDelegate (#766)
Browse files Browse the repository at this point in the history
  • Loading branch information
icalvo authored May 25, 2023
1 parent 0ec70a4 commit 35ce60b
Show file tree
Hide file tree
Showing 13 changed files with 859 additions and 675 deletions.
2 changes: 1 addition & 1 deletion examples/Cli/Delegates/BarSettings.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public sealed class BarSettings : CommandSettings
{
[CommandOption("--count")]
[Description("The number of bars to print")]
[DefaultValue(1)]
[DefaultValue(3)]
public int Count { get; set; }
}
}
25 changes: 24 additions & 1 deletion examples/Cli/Delegates/Program.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Threading.Tasks;
using Spectre.Console;
using Spectre.Console.Cli;

Expand All @@ -14,7 +15,13 @@ public static int Main(string[] args)
.WithDescription("Foos the bars");

config.AddDelegate<BarSettings>("bar", Bar)
.WithDescription("Bars the foos"); ;
.WithDescription("Bars the foos");

config.AddAsyncDelegate("fooAsync", FooAsync)
.WithDescription("Foos the bars asynchronously");

config.AddAsyncDelegate<BarSettings>("barAsync", BarAsync)
.WithDescription("Bars the foos asynchronously");
});

return app.Run(args);
Expand All @@ -35,4 +42,20 @@ private static int Bar(CommandContext context, BarSettings settings)

return 0;
}

private static Task<int> FooAsync(CommandContext context)
{
AnsiConsole.WriteLine("Foo");
return Task.FromResult(0);
}

private static Task<int> BarAsync(CommandContext context, BarSettings settings)
{
for (var index = 0; index < settings.Count; index++)
{
AnsiConsole.WriteLine("Bar");
}

return Task.FromResult(0);
}
}
2 changes: 1 addition & 1 deletion examples/Console/Json/Json.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@
</PropertyGroup>

<ItemGroup>
<ProjectReference Include="..\..\Shared\Shared.csproj" />
<ProjectReference Include="..\..\..\src\Spectre.Console.Json\Spectre.Console.Json.csproj" />
<ProjectReference Include="..\..\Shared\Shared.csproj" />
</ItemGroup>

</Project>
1,127 changes: 571 additions & 556 deletions examples/Examples.sln

Large diffs are not rendered by default.

42 changes: 42 additions & 0 deletions src/Spectre.Console.Cli/ConfiguratorExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,26 @@ public static ICommandConfigurator AddDelegate(
return configurator.AddDelegate<EmptyCommandSettings>(name, (c, _) => func(c));
}

/// <summary>
/// Adds a command without settings that executes an async delegate.
/// </summary>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddAsyncDelegate(
this IConfigurator configurator,
string name,
Func<CommandContext, Task<int>> func)
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}

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

/// <summary>
/// Adds a command without settings that executes a delegate.
/// </summary>
Expand All @@ -259,6 +279,28 @@ public static ICommandConfigurator AddDelegate<TSettings>(
return configurator.AddDelegate<TSettings>(name, (c, _) => func(c));
}

/// <summary>
/// Adds a command without settings that executes an async delegate.
/// </summary>
/// <typeparam name="TSettings">The command setting type.</typeparam>
/// <param name="configurator">The configurator.</param>
/// <param name="name">The name of the command.</param>
/// <param name="func">The delegate to execute as part of command execution.</param>
/// <returns>A command configurator that can be used to configure the command further.</returns>
public static ICommandConfigurator AddAsyncDelegate<TSettings>(
this IConfigurator<TSettings> configurator,
string name,
Func<CommandContext, Task<int>> func)
where TSettings : CommandSettings
{
if (configurator == null)
{
throw new ArgumentNullException(nameof(configurator));
}

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

/// <summary>
/// Sets the ExceptionsHandler.
/// <para>Setting <see cref="ICommandAppSettings.ExceptionHandler"/> this way will use the
Expand Down
10 changes: 10 additions & 0 deletions src/Spectre.Console.Cli/IConfigurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ ICommandConfigurator AddCommand<TCommand>(string name)
ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, 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 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, 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 @@ -57,6 +57,16 @@ ICommandConfigurator AddCommand<TCommand>(string name)
ICommandConfigurator AddDelegate<TDerivedSettings>(string name, Func<CommandContext, TDerivedSettings, 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 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, Task<int>> func)
where TDerivedSettings : TSettings;

/// <summary>
/// Adds a command branch.
/// </summary>
Expand Down
10 changes: 9 additions & 1 deletion src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public Configurator(ITypeRegistrar registrar)
public void AddExample(params string[] args)
{
Examples.Add(args);
}
}

public ConfiguredCommand SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommand
Expand All @@ -42,6 +42,14 @@ public ICommandConfigurator AddCommand<TCommand>(string name)

public ICommandConfigurator AddDelegate<TSettings>(string name, Func<CommandContext, TSettings, int> func)
where TSettings : CommandSettings
{
var command = Commands.AddAndReturn(ConfiguredCommand.FromDelegate<TSettings>(
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)));
Expand Down
209 changes: 110 additions & 99 deletions src/Spectre.Console.Cli/Internal/Configuration/ConfiguratorOfT.cs
Original file line number Diff line number Diff line change
@@ -1,100 +1,111 @@
namespace Spectre.Console.Cli;

internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings>
where TSettings : CommandSettings
{
private readonly ConfiguredCommand _command;
private readonly ITypeRegistrar? _registrar;

public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar)
{
_command = command;
_registrar = registrar;
}

public void SetDescription(string description)
{
_command.Description = description;
}

public void AddExample(string[] args)
{
_command.Examples.Add(args);
}

public void SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommandLimiter<TSettings>
{
var defaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
CliConstants.DefaultCommandName, isDefaultCommand: true);
_command.Children.Add(defaultCommand);
}

public void HideBranch()
{
_command.IsHidden = true;
}

public ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>
{
var command = ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false);
var configurator = new CommandConfigurator(command);

_command.Children.Add(command);
return configurator;
}

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

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

public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
action(new Configurator<TDerivedSettings>(command, _registrar));
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}

ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
{
var method = GetType().GetMethod("AddCommand");
if (method == null)
{
throw new CommandConfigurationException("Could not find AddCommand by reflection.");
}

method = method.MakeGenericMethod(command);

if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result))
{
throw new CommandConfigurationException("Invoking AddCommand returned null.");
}

return result;
}

IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
{
var command = ConfiguredCommand.FromBranch(settings, name);

// Create the configurator.
var configuratorType = typeof(Configurator<>).MakeGenericType(settings);
if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator))
{
throw new CommandConfigurationException("Could not create configurator by reflection.");
}

action(configurator);
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}
namespace Spectre.Console.Cli;

internal sealed class Configurator<TSettings> : IUnsafeBranchConfigurator, IConfigurator<TSettings>
where TSettings : CommandSettings
{
private readonly ConfiguredCommand _command;
private readonly ITypeRegistrar? _registrar;

public Configurator(ConfiguredCommand command, ITypeRegistrar? registrar)
{
_command = command;
_registrar = registrar;
}

public void SetDescription(string description)
{
_command.Description = description;
}

public void AddExample(string[] args)
{
_command.Examples.Add(args);
}

public void SetDefaultCommand<TDefaultCommand>()
where TDefaultCommand : class, ICommandLimiter<TSettings>
{
var defaultCommand = ConfiguredCommand.FromType<TDefaultCommand>(
CliConstants.DefaultCommandName, isDefaultCommand: true);

_command.Children.Add(defaultCommand);
}

public void HideBranch()
{
_command.IsHidden = true;
}

public ICommandConfigurator AddCommand<TCommand>(string name)
where TCommand : class, ICommandLimiter<TSettings>
{
var command = ConfiguredCommand.FromType<TCommand>(name, isDefaultCommand: false);
var configurator = new CommandConfigurator(command);

_command.Children.Add(command);
return configurator;
}

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

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

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

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

public IBranchConfigurator AddBranch<TDerivedSettings>(string name, Action<IConfigurator<TDerivedSettings>> action)
where TDerivedSettings : TSettings
{
var command = ConfiguredCommand.FromBranch<TDerivedSettings>(name);
action(new Configurator<TDerivedSettings>(command, _registrar));
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}

ICommandConfigurator IUnsafeConfigurator.AddCommand(string name, Type command)
{
var method = GetType().GetMethod("AddCommand");
if (method == null)
{
throw new CommandConfigurationException("Could not find AddCommand by reflection.");
}

method = method.MakeGenericMethod(command);

if (!(method.Invoke(this, new object[] { name }) is ICommandConfigurator result))
{
throw new CommandConfigurationException("Invoking AddCommand returned null.");
}

return result;
}

IBranchConfigurator IUnsafeConfigurator.AddBranch(string name, Type settings, Action<IUnsafeBranchConfigurator> action)
{
var command = ConfiguredCommand.FromBranch(settings, name);

// Create the configurator.
var configuratorType = typeof(Configurator<>).MakeGenericType(settings);
if (!(Activator.CreateInstance(configuratorType, new object?[] { command, _registrar }) is IUnsafeBranchConfigurator configurator))
{
throw new CommandConfigurationException("Could not create configurator by reflection.");
}

action(configurator);
var added = _command.Children.AddAndReturn(command);
return new BranchConfigurator(added);
}
}
Loading

0 comments on commit 35ce60b

Please sign in to comment.