diff --git a/src/Spectre.Console.Cli/CommandApp.cs b/src/Spectre.Console.Cli/CommandApp.cs index 8ea2c63..4882fc0 100644 --- a/src/Spectre.Console.Cli/CommandApp.cs +++ b/src/Spectre.Console.Cli/CommandApp.cs @@ -1,3 +1,5 @@ +using Spectre.Console.Cli.Internal.Configuration; + namespace Spectre.Console.Cli; /// <summary> @@ -39,10 +41,11 @@ public sealed class CommandApp : ICommandApp /// Sets the default command. /// </summary> /// <typeparam name="TCommand">The command type.</typeparam> - public void SetDefaultCommand<TCommand>() + /// <returns>A <see cref="DefaultCommandConfigurator"/> that can be used to configure the default command.</returns> + public DefaultCommandConfigurator SetDefaultCommand<TCommand>() where TCommand : class, ICommand { - GetConfigurator().SetDefaultCommand<TCommand>(); + return new DefaultCommandConfigurator(GetConfigurator().SetDefaultCommand<TCommand>()); } /// <summary> diff --git a/src/Spectre.Console.Cli/CommandAppOfT.cs b/src/Spectre.Console.Cli/CommandAppOfT.cs index b4a1287..295011e 100644 --- a/src/Spectre.Console.Cli/CommandAppOfT.cs +++ b/src/Spectre.Console.Cli/CommandAppOfT.cs @@ -1,3 +1,5 @@ +using Spectre.Console.Cli.Internal.Configuration; + namespace Spectre.Console.Cli; /// <summary> @@ -8,6 +10,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp where TDefaultCommand : class, ICommand { private readonly CommandApp _app; + private readonly DefaultCommandConfigurator _defaultCommandConfigurator; /// <summary> /// Initializes a new instance of the <see cref="CommandApp{TDefaultCommand}"/> class. @@ -16,7 +19,7 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp public CommandApp(ITypeRegistrar? registrar = null) { _app = new CommandApp(registrar); - _app.GetConfigurator().SetDefaultCommand<TDefaultCommand>(); + _defaultCommandConfigurator = _app.SetDefaultCommand<TDefaultCommand>(); } /// <summary> @@ -46,5 +49,32 @@ public sealed class CommandApp<TDefaultCommand> : ICommandApp public Task<int> RunAsync(IEnumerable<string> args) { return _app.RunAsync(args); + } + + internal Configurator GetConfigurator() + { + return _app.GetConfigurator(); + } + + /// <summary> + /// Sets the description of the default command. + /// </summary> + /// <param name="description">The default command description.</param> + /// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns> + public CommandApp<TDefaultCommand> WithDescription(string description) + { + _defaultCommandConfigurator.WithDescription(description); + return this; + } + + /// <summary> + /// Sets data that will be passed to the command via the <see cref="CommandContext"/>. + /// </summary> + /// <param name="data">The data to pass to the default command.</param> + /// <returns>The same <see cref="CommandApp{TDefaultCommand}"/> instance so that multiple calls can be chained.</returns> + public CommandApp<TDefaultCommand> WithData(object data) + { + _defaultCommandConfigurator.WithData(data); + return this; } } \ No newline at end of file diff --git a/src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs b/src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs index 746393c..cd1b42d 100644 --- a/src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs +++ b/src/Spectre.Console.Cli/Internal/Configuration/Configurator.cs @@ -25,11 +25,12 @@ internal sealed class Configurator : IUnsafeConfigurator, IConfigurator, IConfig Examples.Add(args); } - public void SetDefaultCommand<TDefaultCommand>() + public ConfiguredCommand SetDefaultCommand<TDefaultCommand>() where TDefaultCommand : class, ICommand { DefaultCommand = ConfiguredCommand.FromType<TDefaultCommand>( CliConstants.DefaultCommandName, isDefaultCommand: true); + return DefaultCommand; } public ICommandConfigurator AddCommand<TCommand>(string name) diff --git a/src/Spectre.Console.Cli/Internal/Configuration/DefaultCommandConfigurator.cs b/src/Spectre.Console.Cli/Internal/Configuration/DefaultCommandConfigurator.cs new file mode 100644 index 0000000..a11432b --- /dev/null +++ b/src/Spectre.Console.Cli/Internal/Configuration/DefaultCommandConfigurator.cs @@ -0,0 +1,36 @@ +namespace Spectre.Console.Cli.Internal.Configuration; + +/// <summary> +/// Fluent configurator for the default command. +/// </summary> +public sealed class DefaultCommandConfigurator +{ + private readonly ConfiguredCommand _defaultCommand; + + internal DefaultCommandConfigurator(ConfiguredCommand defaultCommand) + { + _defaultCommand = defaultCommand; + } + + /// <summary> + /// Sets the description of the default command. + /// </summary> + /// <param name="description">The default command description.</param> + /// <returns>The same <see cref="DefaultCommandConfigurator"/> instance so that multiple calls can be chained.</returns> + public DefaultCommandConfigurator WithDescription(string description) + { + _defaultCommand.Description = description; + return this; + } + + /// <summary> + /// Sets data that will be passed to the command via the <see cref="CommandContext"/>. + /// </summary> + /// <param name="data">The data to pass to the default command.</param> + /// <returns>The same <see cref="DefaultCommandConfigurator"/> instance so that multiple calls can be chained.</returns> + public DefaultCommandConfigurator WithData(object data) + { + _defaultCommand.Data = data; + return this; + } +} \ No newline at end of file diff --git a/src/Spectre.Console.Testing/Cli/CommandAppTester.cs b/src/Spectre.Console.Testing/Cli/CommandAppTester.cs index 8143e1c..d06ccd4 100644 --- a/src/Spectre.Console.Testing/Cli/CommandAppTester.cs +++ b/src/Spectre.Console.Testing/Cli/CommandAppTester.cs @@ -25,11 +25,25 @@ public sealed class CommandAppTester /// <summary> /// Sets the default command. /// </summary> + /// <param name="description">The optional default command description.</param> + /// <param name="data">The optional default command data.</param> /// <typeparam name="T">The default command type.</typeparam> - public void SetDefaultCommand<T>() + public void SetDefaultCommand<T>(string? description = null, object? data = null) where T : class, ICommand { - _appConfiguration = (app) => app.SetDefaultCommand<T>(); + _appConfiguration = (app) => + { + var defaultCommandBuilder = app.SetDefaultCommand<T>(); + if (description != null) + { + defaultCommandBuilder.WithDescription(description); + } + + if (data != null) + { + defaultCommandBuilder.WithData(data); + } + }; } /// <summary> diff --git a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.cs b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.cs index 9021b3c..2f69a6b 100644 --- a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.cs +++ b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.cs @@ -845,30 +845,6 @@ public sealed partial class CommandAppTests result.Context.ShouldHaveRemainingArgument("foo", values: new[] { (string)null }); } - [Fact] - public void Should_Be_Able_To_Set_The_Default_Command() - { - // Given - var app = new CommandAppTester(); - app.SetDefaultCommand<DogCommand>(); - - // When - var result = app.Run(new[] - { - "4", "12", "--good-boy", "--name", "Rufus", - }); - - // Then - result.ExitCode.ShouldBe(0); - result.Settings.ShouldBeOfType<DogSettings>().And(dog => - { - dog.Legs.ShouldBe(4); - dog.Age.ShouldBe(12); - dog.GoodBoy.ShouldBe(true); - dog.Name.ShouldBe("Rufus"); - }); - } - [Fact] public void Should_Set_Command_Name_In_Context() { @@ -917,6 +893,66 @@ public sealed partial class CommandAppTests // Then result.Context.ShouldNotBeNull(); result.Context.Data.ShouldBe(123); + } + + public sealed class Default_Command + { + [Fact] + public void Should_Be_Able_To_Set_The_Default_Command() + { + // Given + var app = new CommandAppTester(); + app.SetDefaultCommand<DogCommand>(); + + // When + var result = app.Run(new[] + { + "4", "12", "--good-boy", "--name", "Rufus", + }); + + // Then + result.ExitCode.ShouldBe(0); + result.Settings.ShouldBeOfType<DogSettings>().And(dog => + { + dog.Legs.ShouldBe(4); + dog.Age.ShouldBe(12); + dog.GoodBoy.ShouldBe(true); + dog.Name.ShouldBe("Rufus"); + }); + } + + [Fact] + public void Should_Set_The_Default_Command_Description_Data_CommandApp() + { + // Given + var app = new CommandApp(); + app.SetDefaultCommand<DogCommand>() + .WithDescription("The default command") + .WithData(new string[] { "foo", "bar" }); + + // When + + // Then + app.GetConfigurator().DefaultCommand.ShouldNotBeNull(); + app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command"); + app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" }); + } + + [Fact] + public void Should_Set_The_Default_Command_Description_Data_CommandAppOfT() + { + // Given + var app = new CommandApp<DogCommand>() + .WithDescription("The default command") + .WithData(new string[] { "foo", "bar" }); + + // When + + // Then + app.GetConfigurator().DefaultCommand.ShouldNotBeNull(); + app.GetConfigurator().DefaultCommand.Description.ShouldBe("The default command"); + app.GetConfigurator().DefaultCommand.Data.ShouldBe(new string[] { "foo", "bar" }); + } } public sealed class Delegate_Commands @@ -930,7 +966,7 @@ public sealed partial class CommandAppTests var app = new CommandApp(); app.Configure(config => - { + { config.PropagateExceptions(); config.AddDelegate<DogSettings>( "foo", (context, settings) =>