From 4a8a4ab048f70c155340cf238a9a4de481185c1d Mon Sep 17 00:00:00 2001 From: krisrok <3404365+krisrok@users.noreply.github.com> Date: Wed, 14 Dec 2022 18:19:12 +0100 Subject: [PATCH] Show help for default command (#953) Shows help if the default command was called without any arguments but has required arguments. This way it does not hinder execution of a default command without any required arguments. When called without args: - Default command with required params -> Show help - Default command with required params and additional commands -> Show help including additional commands - Default command without required params -> Execute command --- .../Internal/CommandExecutor.cs | 8 + .../Data/Commands/GreeterCommand.cs | 17 + .../Default_Without_Args.Output.verified.txt | 16 + ...ithout_Args_Additional.Output.verified.txt | 19 + .../Help/Greeter_Default.Output.verified.txt | 1 + .../Spectre.Console.Cli.Tests.csproj | 11 + .../Unit/CommandAppTests.Help.cs | 696 ++++++++++-------- 7 files changed, 449 insertions(+), 319 deletions(-) create mode 100644 test/Spectre.Console.Cli.Tests/Data/Commands/GreeterCommand.cs create mode 100644 test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt create mode 100644 test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt create mode 100644 test/Spectre.Console.Cli.Tests/Expectations/Help/Greeter_Default.Output.verified.txt diff --git a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs index 3194755..e482652 100644 --- a/src/Spectre.Console.Cli/Internal/CommandExecutor.cs +++ b/src/Spectre.Console.Cli/Internal/CommandExecutor.cs @@ -66,6 +66,14 @@ internal sealed class CommandExecutor return leaf.ShowHelp ? 0 : 1; } + // Is this the default and is it called without arguments when there are required arguments? + if (leaf.Command.IsDefaultCommand && args.Count() == 0 && leaf.Command.Parameters.Any(p => p.Required)) + { + // Display help for default command. + configuration.Settings.Console.SafeRender(HelpWriter.WriteCommand(model, leaf.Command)); + return 1; + } + // Register the arguments with the container. _registrar.RegisterInstance(typeof(IRemainingArguments), parsedResult.Remaining); diff --git a/test/Spectre.Console.Cli.Tests/Data/Commands/GreeterCommand.cs b/test/Spectre.Console.Cli.Tests/Data/Commands/GreeterCommand.cs new file mode 100644 index 0000000..7e3e653 --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Data/Commands/GreeterCommand.cs @@ -0,0 +1,17 @@ +using Spectre.Console; + +public class GreeterCommand : Command +{ + private readonly IAnsiConsole _console; + + public GreeterCommand(IAnsiConsole console) + { + _console = console; + } + + public override int Execute([NotNull] CommandContext context, [NotNull] OptionalArgumentWithDefaultValueSettings settings) + { + _console.WriteLine(settings.Greeting); + return 0; + } +} \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt new file mode 100644 index 0000000..1794d66 --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args.Output.verified.txt @@ -0,0 +1,16 @@ +DESCRIPTION: +The lion command. + +USAGE: + myapp [LEGS] [OPTIONS] + +ARGUMENTS: + The number of teeth the lion has + [LEGS] The number of legs + +OPTIONS: + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive + -n, --name + --agility The agility between 0 and 100 + -c The number of children the lion has \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt new file mode 100644 index 0000000..36cb2f6 --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Default_Without_Args_Additional.Output.verified.txt @@ -0,0 +1,19 @@ +DESCRIPTION: +The lion command. + +USAGE: + myapp [LEGS] [OPTIONS] + +ARGUMENTS: + The number of teeth the lion has + [LEGS] The number of legs + +OPTIONS: + -h, --help Prints help information + -a, --alive Indicates whether or not the animal is alive + -n, --name + --agility The agility between 0 and 100 + -c The number of children the lion has + +COMMANDS: + giraffe The giraffe command \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Expectations/Help/Greeter_Default.Output.verified.txt b/test/Spectre.Console.Cli.Tests/Expectations/Help/Greeter_Default.Output.verified.txt new file mode 100644 index 0000000..5e1c309 --- /dev/null +++ b/test/Spectre.Console.Cli.Tests/Expectations/Help/Greeter_Default.Output.verified.txt @@ -0,0 +1 @@ +Hello World \ No newline at end of file diff --git a/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj b/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj index 6f68434..6bc5b2a 100644 --- a/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj +++ b/test/Spectre.Console.Cli.Tests/Spectre.Console.Cli.Tests.csproj @@ -29,4 +29,15 @@ + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + %(ParentFile).cs + + + $([System.String]::Copy('%(FileName)').Split('.')[0]) + %(ParentFile).cs + + + diff --git a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs index 87a9d3a..5def4e4 100644 --- a/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs +++ b/test/Spectre.Console.Cli.Tests/Unit/CommandAppTests.Help.cs @@ -1,319 +1,377 @@ -using Spectre.Console.Cli; - -namespace Spectre.Console.Tests.Unit.Cli; - -public sealed partial class CommandAppTests -{ - [UsesVerify] - [ExpectationPath("Help")] - public class Help - { - [Fact] - [Expectation("Root")] - public Task Should_Output_Root_Correctly() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddCommand("dog"); - configurator.AddCommand("horse"); - configurator.AddCommand("giraffe"); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("Hidden_Commands")] - public Task Should_Skip_Hidden_Commands() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddCommand("dog"); - configurator.AddCommand("horse"); - configurator.AddCommand("giraffe") - .WithExample(new[] { "giraffe", "123" }) - .IsHidden(); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("Description_No_Trailing_Period")] - public Task Should_Not_Trim_Description_Trailing_Period() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddCommand("dog"); - configurator.AddCommand("horse"); - configurator.AddCommand("giraffe") - .WithExample(new[] { "giraffe", "123" }) - .IsHidden(); - configurator.TrimTrailingPeriods(false); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("Command")] - public Task Should_Output_Command_Correctly() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddBranch("cat", animal => - { - animal.SetDescription("Contains settings for a cat."); - animal.AddCommand("lion"); - }); - }); - - // When - var result = fixture.Run("cat", "--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("Leaf")] - public Task Should_Output_Leaf_Correctly() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddBranch("cat", animal => - { - animal.SetDescription("Contains settings for a cat."); - animal.AddCommand("lion"); - }); - }); - - // When - var result = fixture.Run("cat", "lion", "--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("Default")] - public Task Should_Output_Default_Command_Correctly() - { - // Given - var fixture = new CommandAppTester(); - fixture.SetDefaultCommand(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("RootExamples")] - public Task Should_Output_Root_Examples_Defined_On_Root() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); - configurator.AddExample(new[] { "horse", "--name", "Brutus" }); - configurator.AddCommand("dog"); - configurator.AddCommand("horse"); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("RootExamples_Children")] - public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddCommand("dog") - .WithExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); - configurator.AddCommand("horse") - .WithExample(new[] { "horse", "--name", "Brutus" }); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("RootExamples_Leafs")] - public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddBranch("animal", animal => - { - animal.SetDescription("The animal command."); - animal.AddCommand("dog") - .WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); - animal.AddCommand("horse") - .WithExample(new[] { "animal", "horse", "--name", "Brutus" }); - }); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("CommandExamples")] - public Task Should_Only_Output_Command_Examples_Defined_On_Command() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddBranch("animal", animal => - { - animal.SetDescription("The animal command."); - animal.AddExample(new[] { "animal", "--help" }); - - animal.AddCommand("dog") - .WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); - animal.AddCommand("horse") - .WithExample(new[] { "animal", "horse", "--name", "Brutus" }); - }); - }); - - // When - var result = fixture.Run("animal", "--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("DefaultExamples")] - public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified() - { - // Given - var fixture = new CommandAppTester(); - fixture.SetDefaultCommand(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddExample(new[] { "12", "-c", "3" }); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("NoDescription")] - public Task Should_Not_Show_Truncated_Command_Table_If_Commands_Are_Missing_Description() - { - // Given - var fixture = new CommandAppTester(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - configurator.AddCommand("bar"); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("ArgumentOrder")] - public Task Should_List_Arguments_In_Correct_Order() - { - // Given - var fixture = new CommandAppTester(); - fixture.SetDefaultCommand>(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - - [Fact] - [Expectation("Hidden_Command_Options")] - public Task Should_Not_Show_Hidden_Command_Options() - { - // Given - var fixture = new CommandAppTester(); - fixture.SetDefaultCommand>(); - fixture.Configure(configurator => - { - configurator.SetApplicationName("myapp"); - }); - - // When - var result = fixture.Run("--help"); - - // Then - return Verifier.Verify(result.Output); - } - } -} +using Spectre.Console.Cli; + +namespace Spectre.Console.Tests.Unit.Cli; + +public sealed partial class CommandAppTests +{ + [UsesVerify] + [ExpectationPath("Help")] + public class Help + { + [Fact] + [Expectation("Root")] + public Task Should_Output_Root_Correctly() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddCommand("dog"); + configurator.AddCommand("horse"); + configurator.AddCommand("giraffe"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Hidden_Commands")] + public Task Should_Skip_Hidden_Commands() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddCommand("dog"); + configurator.AddCommand("horse"); + configurator.AddCommand("giraffe") + .WithExample(new[] { "giraffe", "123" }) + .IsHidden(); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Description_No_Trailing_Period")] + public Task Should_Not_Trim_Description_Trailing_Period() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddCommand("dog"); + configurator.AddCommand("horse"); + configurator.AddCommand("giraffe") + .WithExample(new[] { "giraffe", "123" }) + .IsHidden(); + configurator.TrimTrailingPeriods(false); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Command")] + public Task Should_Output_Command_Correctly() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddBranch("cat", animal => + { + animal.SetDescription("Contains settings for a cat."); + animal.AddCommand("lion"); + }); + }); + + // When + var result = fixture.Run("cat", "--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Leaf")] + public Task Should_Output_Leaf_Correctly() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddBranch("cat", animal => + { + animal.SetDescription("Contains settings for a cat."); + animal.AddCommand("lion"); + }); + }); + + // When + var result = fixture.Run("cat", "lion", "--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Default")] + public Task Should_Output_Default_Command_Correctly() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Default_Without_Args")] + public Task Should_Output_Default_Command_When_Command_Has_Required_Parameters_And_Is_Called_Without_Args() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var result = fixture.Run(); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Default_Without_Args_Additional")] + public Task Should_Output_Default_Command_And_Additional_Commands_When_Default_Command_Has_Required_Parameters_And_Is_Called_Without_Args() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddCommand("giraffe"); + }); + + // When + var result = fixture.Run(); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Greeter_Default")] + public Task Should_Not_Output_Default_Command_When_Command_Has_No_Required_Parameters_And_Is_Called_Without_Args() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var result = fixture.Run(); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("RootExamples")] + public Task Should_Output_Root_Examples_Defined_On_Root() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); + configurator.AddExample(new[] { "horse", "--name", "Brutus" }); + configurator.AddCommand("dog"); + configurator.AddCommand("horse"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("RootExamples_Children")] + public Task Should_Output_Root_Examples_Defined_On_Direct_Children_If_Root_Have_No_Examples() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddCommand("dog") + .WithExample(new[] { "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); + configurator.AddCommand("horse") + .WithExample(new[] { "horse", "--name", "Brutus" }); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("RootExamples_Leafs")] + public Task Should_Output_Root_Examples_Defined_On_Leaves_If_No_Other_Examples_Are_Found() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddBranch("animal", animal => + { + animal.SetDescription("The animal command."); + animal.AddCommand("dog") + .WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); + animal.AddCommand("horse") + .WithExample(new[] { "animal", "horse", "--name", "Brutus" }); + }); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("CommandExamples")] + public Task Should_Only_Output_Command_Examples_Defined_On_Command() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddBranch("animal", animal => + { + animal.SetDescription("The animal command."); + animal.AddExample(new[] { "animal", "--help" }); + + animal.AddCommand("dog") + .WithExample(new[] { "animal", "dog", "--name", "Rufus", "--age", "12", "--good-boy" }); + animal.AddCommand("horse") + .WithExample(new[] { "animal", "horse", "--name", "Brutus" }); + }); + }); + + // When + var result = fixture.Run("animal", "--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("DefaultExamples")] + public Task Should_Output_Root_Examples_If_Default_Command_Is_Specified() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddExample(new[] { "12", "-c", "3" }); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("NoDescription")] + public Task Should_Not_Show_Truncated_Command_Table_If_Commands_Are_Missing_Description() + { + // Given + var fixture = new CommandAppTester(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + configurator.AddCommand("bar"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("ArgumentOrder")] + public Task Should_List_Arguments_In_Correct_Order() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand>(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + + [Fact] + [Expectation("Hidden_Command_Options")] + public Task Should_Not_Show_Hidden_Command_Options() + { + // Given + var fixture = new CommandAppTester(); + fixture.SetDefaultCommand>(); + fixture.Configure(configurator => + { + configurator.SetApplicationName("myapp"); + }); + + // When + var result = fixture.Run("--help"); + + // Then + return Verifier.Verify(result.Output); + } + } +} \ No newline at end of file